commit 9ec374e812f52f0a85fabaef60ec4ca94eb0edf0
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Nov 16 13:04:47 2020 +0100

    More support for using the library from more than one activity

diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueue.java b/src/main/java/org/distorted/library/effectqueue/EffectQueue.java
index 183e6b4..a6da58a 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueue.java
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueue.java
@@ -24,6 +24,7 @@ import org.distorted.library.effect.EffectName;
 import org.distorted.library.effect.EffectType;
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.main.InternalMaster;
+import org.distorted.library.main.InternalStackFrameList;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -239,7 +240,7 @@ public abstract class EffectQueue implements InternalMaster.Slave
 
   public static boolean setMax(int index, int m)
     {
-    if( !DistortedLibrary.isInitialized() || m<=mMax[index] )
+    if( !InternalStackFrameList.isInitialized() || m<=mMax[index] )
       {
       mMax[index] = m<0 ? 0:m;
       return true;
diff --git a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
index d3100dc..120e5c1 100644
--- a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
+++ b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
@@ -236,9 +236,9 @@ public class DistortedFramebuffer extends InternalOutputSurface
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // create a multi-framebuffer (1 object containing multiple FBOs)
 
-  DistortedFramebuffer(int numfbos, int numcolors, int depthStencil, int type, int width, int height)
+  DistortedFramebuffer(int numfbos, int numcolors, int depthStencil, int type, int storage, int width, int height)
     {
-    super(width,height,NOT_CREATED_YET,numfbos,numcolors,depthStencil,NOT_CREATED_YET, type);
+    super(width,height,NOT_CREATED_YET,numfbos,numcolors,depthStencil,NOT_CREATED_YET, type, storage);
     markForCreation();
     }
 
@@ -250,7 +250,7 @@ public class DistortedFramebuffer extends InternalOutputSurface
 
   DistortedFramebuffer(int numcolors, int depthStencil, int type, int width, int height)
     {
-    this(1,numcolors,depthStencil,type,width,height);
+    this(1,numcolors,depthStencil,type,STORAGE_PRIVATE,width,height);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -268,7 +268,7 @@ public class DistortedFramebuffer extends InternalOutputSurface
   @SuppressWarnings("unused")
   public DistortedFramebuffer(int width, int height, int numcolors, int depthStencil)
     {
-    this(1,numcolors,depthStencil,TYPE_USER,width,height);
+    this(1,numcolors,depthStencil,TYPE_USER,STORAGE_PRIVATE,width,height);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.java b/src/main/java/org/distorted/library/main/DistortedLibrary.java
index a1ce06b..31b0677 100644
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.java
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.java
@@ -57,9 +57,6 @@ import java.util.regex.Pattern;
  */
 public class DistortedLibrary
   {
-  private static int mGLSL;
-  private static String mGLSL_VERSION;
-  private static boolean mOITCompilationAttempted;
   /**
    * When creating an instance of a DistortedTexture from another instance, clone the Bitmap that's
    * backing up our DistortedTexture.
@@ -116,8 +113,10 @@ public class DistortedLibrary
    * https://community.arm.com/graphics/f/discussions/10285/opengl-es-3-1-on-mali-t880-flashes
    */
   private static int mFBOQueueSize;
-
-  private static boolean mInitialized=false;
+  private static int mGLSL;
+  private static String mGLSL_VERSION;
+  private static boolean mOITCompilationAttempted;
+  private static int mMaxTextureSize;
 
   //////////////////////////////////////////////////////////////////////////////////////////////
   /// MAIN PROGRAM ///
@@ -221,7 +220,6 @@ public class DistortedLibrary
 
   private static ExceptionListener mListener;
   private static Resources mResources;
-  private static int mMaxTextureSize;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // private: hide this from Javadoc
@@ -937,16 +935,6 @@ public class DistortedLibrary
     return mGLSL;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Have we called onCreate yet, ie have we initialized the library?
- * @return <code>true</code> if the library is initialized and ready for action.
- */
-  public static boolean isInitialized()
-    {
-    return mInitialized;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * When OpenGL context gets created, call this method so that the library can initialise its internal data structures.
@@ -954,7 +942,7 @@ public class DistortedLibrary
  * <p>
  * Needs to be called from a thread holding the OpenGL context.
  *
- * @param context Context of the App using the library - used to open up Resources and read Shader code.
+ * @param context  Context of the App using the library - used to open up Resources and read Shader code.
  * @param listener The library will send all (asynchronous!) exceptions there.
  */
   public static void onSurfaceCreated(final Context context, final ExceptionListener listener)
@@ -969,8 +957,8 @@ public class DistortedLibrary
  * <p>
  * Needs to be called from a thread holding the OpenGL context.
  *   
- * @param context Context of the App using the library - used to open up Resources and read Shader code.
- * @param listener The library will send all (asynchronous!) exceptions there.
+ * @param context   Context of the App using the library - used to open up Resources and read Shader code.
+ * @param listener  The library will send all (asynchronous!) exceptions there.
  * @param queueSize the size of the FBO queue, a workaround for the bug on Mali drivers. Use a small integer - 1,...,4
  */
   public static void onSurfaceCreated(final Context context, final ExceptionListener listener, int queueSize)
@@ -1002,7 +990,7 @@ public class DistortedLibrary
     android.util.Log.e("DISTORTED", "Using OpenGL ES "+major+"."+minor);
     mGLSL_VERSION = "#version "+mGLSL+" es\n";
 
-    mInitialized = true;
+    InternalStackFrameList.setInitialized(true);
     mOITCompilationAttempted = false;
 
     detectBuggyDriversAndSetQueueSize(queueSize);
@@ -1038,6 +1026,52 @@ public class DistortedLibrary
       }
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can initialize its internal data structures.
+ * Must be called from Activity.onCreate().
+ */
+  public static void onCreate()
+    {
+    onCreate(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can initialize its internal data structures.
+ * Must be called from Activity.onCreate().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onCreate(long id)
+    {
+    InternalStackFrameList.onCreate(id);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can resume its operations.
+ * Must be called from Activity.onResume().
+ */
+  public static void onResume()
+    {
+    onCreate(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can resume its operations.
+ * Must be called from Activity.onResume().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onResume(long id)
+    {
+    InternalStackFrameList.onResume(id);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Call this so that the Library can release the OpenGL related data that needs to be recreated.
@@ -1045,7 +1079,20 @@ public class DistortedLibrary
  */
   public static void onPause()
     {
-    InternalObject.onPause();
+    onPause(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release the OpenGL related data that needs to be recreated.
+ * Must be called from Activity.onPause().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onPause(long id)
+    {
+    InternalStackFrameList.onPause(id);
     Dynamic.onPause();
 
     mLinkedListSSBO[0]= -1;
@@ -1061,21 +1108,39 @@ public class DistortedLibrary
     mOITRenderProgram  = null;
     mBlitDepthProgram  = null;
     mBlitProgram       = null;
+
+
+android.util.Log.e("ON_PAUSE", "id="+id);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Call this so that the Library can release its internal data structures.
- * Must be called from Activity.onDestroy(). 
+ * Must be called from Activity.onDestroy().
  */
   public static void onDestroy()
     {
-    if( mInitialized )
+    onDestroy(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release its internal data structures.
+ * Must be called from Activity.onDestroy().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onDestroy(long id)
+    {
+android.util.Log.e("ON_DESTROY", "id="+id);
+
+    if( InternalStackFrameList.isInitialized() )
       {
-      mInitialized = false;
+      InternalStackFrameList.setInitialized(false);
       mOITCompilationAttempted = false;
 
-      InternalObject.onDestroy();
+      InternalStackFrameList.onDestroy(id);
       InternalNodeData.onDestroy();
       InternalMaster.onDestroy();
       InternalOutputSurface.onDestroy();
diff --git a/src/main/java/org/distorted/library/main/DistortedScreen.java b/src/main/java/org/distorted/library/main/DistortedScreen.java
index a4cd9f5..9007aa0 100644
--- a/src/main/java/org/distorted/library/main/DistortedScreen.java
+++ b/src/main/java/org/distorted/library/main/DistortedScreen.java
@@ -80,7 +80,7 @@ public class DistortedScreen extends DistortedFramebuffer
  */
   public DistortedScreen()
     {
-    super(DistortedLibrary.WAIT_FOR_FBO_QUEUE_SIZE,1,BOTH_DEPTH_STENCIL, TYPE_SYST, 1,1);
+    super(DistortedLibrary.WAIT_FOR_FBO_QUEUE_SIZE,1,BOTH_DEPTH_STENCIL, TYPE_SYST, STORAGE_PRIVATE,1,1);
     mDebugMode = DEBUG_MODE_NONE;
     mCurRenderedFBO = 0;
     mToBeBlittedFBO = 0;
diff --git a/src/main/java/org/distorted/library/main/DistortedTexture.java b/src/main/java/org/distorted/library/main/DistortedTexture.java
index 20ac73f..15cb6c6 100644
--- a/src/main/java/org/distorted/library/main/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/main/DistortedTexture.java
@@ -112,7 +112,7 @@ public class DistortedTexture extends InternalSurface
 
   public DistortedTexture(int type)
     {
-    super(NOT_CREATED_YET,1,1,type);
+    super(NOT_CREATED_YET,1,1,type,InternalObject.STORAGE_PRIVATE);
     mBmp= null;
     }
 
diff --git a/src/main/java/org/distorted/library/main/InternalBuffer.java b/src/main/java/org/distorted/library/main/InternalBuffer.java
index 72057c3..2a6c5bc 100644
--- a/src/main/java/org/distorted/library/main/InternalBuffer.java
+++ b/src/main/java/org/distorted/library/main/InternalBuffer.java
@@ -46,7 +46,7 @@ public class InternalBuffer extends InternalObject
 
   public InternalBuffer(int target, int usage)
     {
-    super(InternalObject.TYPE_USER);
+    super(InternalObject.TYPE_USER, InternalObject.STORAGE_PRIVATE );
 
     mIndex  = new int[1];
     mTarget = target;
@@ -173,6 +173,7 @@ public class InternalBuffer extends InternalObject
       {
       GLES30.glDeleteBuffers(1, mIndex, 0);
       mIndex[0] = -1;
+      removeFromDone();
       }
     }
 
diff --git a/src/main/java/org/distorted/library/main/InternalObject.java b/src/main/java/org/distorted/library/main/InternalObject.java
index f1a670d..579eabe 100644
--- a/src/main/java/org/distorted/library/main/InternalObject.java
+++ b/src/main/java/org/distorted/library/main/InternalObject.java
@@ -19,10 +19,6 @@
 
 package org.distorted.library.main;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Any Object which gets uploaded to GPU memory and thus needs to be re-created (transparently to
@@ -45,144 +41,15 @@ abstract class InternalObject
   static final int TYPE_TREE = 1;
   static final int TYPE_SYST = 2;
 
-  private static final int JOB_CREATE = 0;
-  private static final int JOB_DELETE = 1;
+  static final int STORAGE_COMMON  = 0;
+  static final int STORAGE_PRIVATE = 1;
 
-  private final static Object mLock = new Object();
+  static final int JOB_CREATE = 0;
+  static final int JOB_DELETE = 1;
 
   private long mID;
   private int mType;
-
-  /////////////////////////////////////////////////////////////////
-
-  private static class Job
-    {
-    InternalObject object;
-    int action;
-
-    Job(InternalObject o, int a)
-      {
-      object = o;
-      action = a;
-      }
-    }
-
-  /////////////////////////////////////////////////////////////////
-
-  private static boolean mToDo = false;
-  private static StackFrame mCurrentFrame = null;
-  private static ArrayList<StackFrame> mFrameList = new ArrayList<>();
-
-  /////////////////////////////////////////////////////////////////
-
-  private static class StackFrame
-    {
-    private LinkedList<InternalObject> mDoneList;
-    private HashMap<Long,Job> mToDoMap;
-    private long mNextClientID;
-    private long mNextSystemID;
-
-    /////////////////////////////////////////////////////////
-
-    StackFrame()
-      {
-      mDoneList = new LinkedList<>();
-      mToDoMap  = new HashMap<>();
-      mNextClientID = 0;
-      mNextSystemID = 0;
-      }
-
-    /////////////////////////////////////////////////////////
-
-    void onPause()
-      {
-      int num = mDoneList.size();
-
-      try
-        {
-        for (int i=0; i<num; i++)
-          {
-          InternalObject object = mDoneList.removeFirst();
-          Job job = new Job(object, JOB_CREATE);
-          mToDoMap.put(object.mID,job);
-          object.recreate();
-          }
-        }
-      catch( Exception ignored )
-        {
-        // something else removed an object in the meantime; ignore
-        }
-      }
-
-    /////////////////////////////////////////////////////////
-
-    void toDo()
-      {
-      for(Long key: mToDoMap.keySet())
-        {
-        Job job = mToDoMap.get(key);
-        InternalObject object = job.object;
-
-        if( job.action==JOB_CREATE )
-          {
-          object.create();
-          mDoneList.add(object);
-          }
-        else if( job.action==JOB_DELETE )
-          {
-          object.delete();
-          }
-        }
-
-      mToDoMap.clear();
-      }
-
-    /////////////////////////////////////////////////////////
-
-    long generateID(int type)
-      {
-      return type==TYPE_SYST ? --mNextSystemID : ++mNextClientID;
-      }
-
-    /////////////////////////////////////////////////////////
-
-    void addToDoneList(InternalObject obj)
-      {
-      mDoneList.add(obj);
-      }
-
-
-    /////////////////////////////////////////////////////////
-
-    void markFor(InternalObject obj, long id, int jobType)
-      {
-      mDoneList.remove(obj);
-      mToDoMap.put(id, new Job(obj,jobType) );
-      }
-
-    /////////////////////////////////////////////////////////
-
-    void debugLists(String frameMarker)
-      {
-      android.util.Log.e("Object", frameMarker);
-      android.util.Log.e("Object", "  Done list:");
-
-      for(InternalObject object : mDoneList)
-        {
-        object.print("  ");
-        }
-
-      android.util.Log.e("Object", "  ToDo list:");
-
-      Job job;
-
-      for(Long key: mToDoMap.keySet())
-        {
-        job = mToDoMap.get(key);
-        job.object.print(job.action==JOB_CREATE ? " create":" delete");
-        }
-      }
-    }
+  private int mStorage;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -192,91 +59,8 @@ abstract class InternalObject
   abstract String printDetails();
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context
 
-  static boolean toDo()
-    {
-    if( mToDo )
-      {
-      mToDo = false;
-
-      synchronized(mLock)
-        {
-        mCurrentFrame.toDo();
-        }
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onCreate()
-    {
-    synchronized(mLock)
-      {
-      mCurrentFrame = new StackFrame();
-      mFrameList.add(mCurrentFrame);
-      mToDo = false;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onPause()
-    {
-    synchronized(mLock)
-      {
-      mCurrentFrame.onPause();
-      mToDo = true;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onDestroy()
-    {
-    synchronized(mLock)
-      {
-      int num = mFrameList.size();
-
-      if( num>0 )
-        {
-        mFrameList.remove(num-1);
-
-        if( num>1 )
-          {
-          mCurrentFrame = mFrameList.get(num-2);
-          mToDo = true;
-          }
-        else
-          {
-          mCurrentFrame = null;
-          mToDo = false;
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  static void debugLists()
-    {
-    int num = mFrameList.size();
-    StackFrame frame;
-
-    for(int i=0; i<num; i++)
-      {
-      frame = mFrameList.get(i);
-      frame.debugLists("frame "+i);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void print(String msg)
+  void print(String msg)
     {
     String str = "ID:"+mID;
 
@@ -293,30 +77,32 @@ abstract class InternalObject
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  InternalObject(int type)
+  InternalObject(int type, int storage)
     {
-    if( mCurrentFrame==null ) onCreate();
-
-    mID = mCurrentFrame.generateID(type);
-    mType= type;
+    mType    = type;
+    mStorage = storage;
+    mID      = InternalStackFrameList.getCurrentFrame().generateID(mType,mStorage);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void markWasCreatedImmediately()
     {
-    mCurrentFrame.addToDoneList(this);
+    InternalStackFrameList.getCurrentFrame().addToDoneList(this,mStorage);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void markForCreation()
     {
-    synchronized(mLock)
-      {
-      mCurrentFrame.markFor(this,mID,JOB_CREATE);
-      mToDo = true;
-      }
+    InternalStackFrameList.markFor(this,mID,mStorage,JOB_CREATE);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeFromDone()
+    {
+    InternalStackFrameList.removeFromDone(this,mStorage);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -327,11 +113,7 @@ abstract class InternalObject
  */
   public void markForDeletion()
     {
-    synchronized(mLock)
-      {
-      mCurrentFrame.markFor(this,mID,JOB_DELETE);
-      mToDo = true;
-      }
+    InternalStackFrameList.markFor(this,mID,mStorage,JOB_DELETE);
     }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/InternalOutputSurface.java b/src/main/java/org/distorted/library/main/InternalOutputSurface.java
index 1dede9d..3e59aee 100644
--- a/src/main/java/org/distorted/library/main/InternalOutputSurface.java
+++ b/src/main/java/org/distorted/library/main/InternalOutputSurface.java
@@ -65,9 +65,9 @@ public abstract class InternalOutputSurface extends InternalSurface implements I
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  InternalOutputSurface(int width, int height, int createColor, int numfbos, int numcolors, int depthStencil, int fbo, int type)
+  InternalOutputSurface(int width, int height, int createColor, int numfbos, int numcolors, int depthStencil, int fbo, int type, int storage)
     {
-    super(createColor,numfbos,numcolors,type);
+    super(createColor,numfbos,numcolors,type,storage);
 
     mRenderWayOIT = false;
     mCurrFBO      = 0;
@@ -179,12 +179,12 @@ public abstract class InternalOutputSurface extends InternalSurface implements I
 
     for (int j=0; j<quality; j++) mipmap *= EffectQuality.MULTIPLIER;
 
-    mBuffer[quality] = new DistortedFramebuffer(queueSize,2,BOTH_DEPTH_STENCIL,TYPE_SYST, (int)(width*mipmap), (int)(height*mipmap) );
+    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);
 
-    InternalObject.toDo(); // create the FBOs immediately. This is safe as we must be holding the OpenGL context now.
+    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);
@@ -609,7 +609,7 @@ public abstract class InternalOutputSurface extends InternalSurface implements I
   public int render(long time, int fbo)
     {
     InternalMaster.toDo();
-    toDo();
+    InternalStackFrameList.toDo();
     InternalRenderState.reset();
 
     int numRenders=0, numChildren = mChildren.getNumChildren();
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrame.java b/src/main/java/org/distorted/library/main/InternalStackFrame.java
new file mode 100644
index 0000000..93ca8b8
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalStackFrame.java
@@ -0,0 +1,266 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class InternalStackFrame
+{
+  private static class Job
+    {
+    InternalObject object;
+    int action;
+
+    Job(InternalObject o, int a)
+      {
+      object = o;
+      action = a;
+      }
+    }
+
+  private static LinkedList<InternalObject> mCommonDoneList = new LinkedList<>(); //
+  private static HashMap<Long,Job> mCommonToDoMap           = new HashMap<>();    // Common
+  private static long mCommonNextClientID                   = 0;                  // InternalObject
+  private static long mCommonNextSystemID                   = 0;                  // (postprocessing)
+
+  private LinkedList<InternalObject> mDoneList; //
+  private HashMap<Long,Job> mToDoMap;           //
+  private long mNextClientID;                   // InternalObject
+  private long mNextSystemID;                   //
+  private long mTaskId;                         //
+
+  private static boolean mInitialized;          // DistortedLibrary
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalStackFrame(long taskID)
+    {
+    mDoneList     = new LinkedList<>();
+    mToDoMap      = new HashMap<>();
+    mNextClientID = 0;
+    mNextSystemID = 0;
+    mTaskId       = taskID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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)
+    {
+    android.util.Log.e("Object", frameMarker);
+    android.util.Log.e("Object", "  Done list:");
+
+    for(InternalObject object : list)
+      {
+      object.print("  ");
+      }
+
+    android.util.Log.e("Object", "  ToDo list:");
+
+    Job job;
+
+    for(Long key: map.keySet())
+      {
+      job = map.get(key);
+      job.object.print(job.action==InternalObject.JOB_CREATE ? " create":" delete");
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrameList.java b/src/main/java/org/distorted/library/main/InternalStackFrameList.java
new file mode 100644
index 0000000..20161f9
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalStackFrameList.java
@@ -0,0 +1,210 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class InternalStackFrameList
+{
+  private final static Object mLock = new Object();
+  private static boolean mToDo = false;
+  private static InternalStackFrame mCurrentFrame = null;
+  private static 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;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @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);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 void setInitialized(boolean init)
+    {
+    mCurrentFrame.setInitialized(init);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static boolean isInitialized()
+    {
+    return mCurrentFrame.isInitialized();
+    }
+
+}
diff --git a/src/main/java/org/distorted/library/main/InternalSurface.java b/src/main/java/org/distorted/library/main/InternalSurface.java
index 6949bc4..40c9eca 100644
--- a/src/main/java/org/distorted/library/main/InternalSurface.java
+++ b/src/main/java/org/distorted/library/main/InternalSurface.java
@@ -33,9 +33,9 @@ abstract class InternalSurface extends InternalObject
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  InternalSurface(int create, int numfbos, int numcolors, int type)
+  InternalSurface(int create, int numfbos, int numcolors, int type, int storage)
     {
-    super(type);
+    super(type,storage);
 
     mNumFBOs      = numfbos;
     mNumColors    = numcolors;
