commit bfe45b4a6f87bb5cab00a9895c5f25f53af13003
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu May 2 14:22:23 2019 +0100

    Move all Program-related stuff to Distorted. Now the DistortedEffects class is very simple.

diff --git a/src/main/java/org/distorted/library/main/Distorted.java b/src/main/java/org/distorted/library/main/Distorted.java
index 9f8a445..11f61a9 100644
--- a/src/main/java/org/distorted/library/main/Distorted.java
+++ b/src/main/java/org/distorted/library/main/Distorted.java
@@ -24,10 +24,23 @@ import android.content.Context;
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Resources;
 import android.opengl.GLES31;
+import android.util.Log;
 
+import org.distorted.library.R;
 import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.FragmentEffect;
 import org.distorted.library.effect.PostprocessEffect;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.mesh.MeshBase;
 import org.distorted.library.message.EffectMessageSender;
+import org.distorted.library.program.DistortedProgram;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -94,6 +107,89 @@ public class Distorted
 
   private static boolean mInitialized=false;
 
+  //////////////////////////////////////////////////////////////////////////////////////////////
+  /// MAIN PROGRAM ///
+  private static DistortedProgram mMainProgram;
+  private static int mMainTextureH;
+
+  /// NORMAL PROGRAM /////
+  private static DistortedProgram mNormalProgram;
+  private static int mNormalMVPMatrixH;
+
+  /// MAIN OIT PROGRAM ///
+  private static DistortedProgram mMainOITProgram;
+  private static int mMainOITTextureH;
+  private static int mMainOITSizeH;
+  private static int mMainOITNumRecordsH;
+
+  /// BLIT PROGRAM ///
+  private static DistortedProgram mBlitProgram;
+  private static int mBlitTextureH;
+  private static int mBlitDepthH;
+  private static final FloatBuffer mQuadPositions;
+
+  static
+    {
+    float[] positionData= { -0.5f, -0.5f,  -0.5f, 0.5f,  0.5f,-0.5f,  0.5f, 0.5f };
+    mQuadPositions = ByteBuffer.allocateDirect(32).order(ByteOrder.nativeOrder()).asFloatBuffer();
+    mQuadPositions.put(positionData).position(0);
+    }
+
+  /// BLIT DEPTH PROGRAM ///
+  private static DistortedProgram mBlitDepthProgram;
+  private static int mBlitDepthTextureH;
+  private static int mBlitDepthDepthTextureH;
+  private static int mBlitDepthDepthH;
+  private static int mBlitDepthTexCorrH;
+
+  /// OIT SSBO BUFFER ///
+  private static int[] mLinkedListSSBO = new int[1];
+  private static int[] mAtomicCounter = new int[Distorted.FBO_QUEUE_SIZE];
+  private static int   mCurrBuffer;
+
+  static
+    {
+    mLinkedListSSBO[0]= -1;
+    mCurrBuffer       =  0;
+
+    for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++)  mAtomicCounter[i] = -1;
+    }
+
+  ///////////////////////////////////////////////////////////////
+  // meaning: allocate 1.0 screenful of places for transparent
+  // fragments in the SSBO backing up the OIT render method.
+  private static float mBufferSize=1.0f;
+
+  /// OIT CLEAR PROGRAM ///
+  private static DistortedProgram mOITClearProgram;
+  private static int mOITClearDepthH;
+  private static int mOITClearTexCorrH;
+  private static int mOITClearSizeH;
+
+  /// OIT BUILD PROGRAM ///
+  private static DistortedProgram mOITBuildProgram;
+  private static int mOITBuildTextureH;
+  private static int mOITBuildDepthTextureH;
+  private static int mOITBuildDepthH;
+  private static int mOITBuildTexCorrH;
+  private static int mOITBuildSizeH;
+  private static int mOITBuildNumRecordsH;
+
+  /// OIT COLLAPSE PROGRAM ///
+  private static DistortedProgram mOITCollapseProgram;
+  private static int mOITCollapseDepthTextureH;
+  private static int mOITCollapseDepthH;
+  private static int mOITCollapseTexCorrH;
+  private static int mOITCollapseSizeH;
+
+  /// OIT RENDER PROGRAM ///
+  private static DistortedProgram mOITRenderProgram;
+  private static int mOITRenderDepthH;
+  private static int mOITRenderTexCorrH;
+  private static int mOITRenderSizeH;
+
+  /// END PROGRAMS //////
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // private: hide this from Javadoc
 
@@ -102,6 +198,487 @@ public class Distorted
 
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void createPrograms(Resources resources)
+    {
+    // MAIN PROGRAM ////////////////////////////////////
+    final InputStream mainVertStream = resources.openRawResource(R.raw.main_vertex_shader);
+    final InputStream mainFragStream = resources.openRawResource(R.raw.main_fragment_shader);
+
+    int numF = FragmentEffect.getNumEnabled();
+    int numV = VertexEffect.getNumEnabled();
+
+    String mainVertHeader= Distorted.GLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n");
+    String mainFragHeader= Distorted.GLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n");
+    String enabledEffectV= VertexEffect.getGLSL();
+    String enabledEffectF= FragmentEffect.getGLSL();
+
+    String[] feedback = { "v_Position", "v_endPosition" };
+
+    try
+      {
+      mMainProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader, mainFragHeader,
+                                          enabledEffectV, enabledEffectF, Distorted.GLSL, feedback);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile MAIN program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int mainProgramH = mMainProgram.getProgramHandle();
+    EffectQueue.getUniforms(mainProgramH,0);
+    mMainTextureH= GLES31.glGetUniformLocation( mainProgramH, "u_Texture");
+
+    // BLIT PROGRAM ////////////////////////////////////
+    final InputStream blitVertStream = resources.openRawResource(R.raw.blit_vertex_shader);
+    final InputStream blitFragStream = resources.openRawResource(R.raw.blit_fragment_shader);
+
+    try
+      {
+      mBlitProgram = new DistortedProgram(blitVertStream,blitFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile BLIT program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int blitProgramH = mBlitProgram.getProgramHandle();
+    mBlitTextureH  = GLES31.glGetUniformLocation( blitProgramH, "u_Texture");
+    mBlitDepthH    = GLES31.glGetUniformLocation( blitProgramH, "u_Depth");
+
+    // BLIT DEPTH PROGRAM ////////////////////////////////////
+    final InputStream blitDepthVertStream = resources.openRawResource(R.raw.blit_depth_vertex_shader);
+    final InputStream blitDepthFragStream = resources.openRawResource(R.raw.blit_depth_fragment_shader);
+
+    try
+      {
+      mBlitDepthProgram = new DistortedProgram(blitDepthVertStream,blitDepthFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile BLIT DEPTH program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int blitDepthProgramH   = mBlitDepthProgram.getProgramHandle();
+    mBlitDepthTextureH      = GLES31.glGetUniformLocation( blitDepthProgramH, "u_Texture");
+    mBlitDepthDepthTextureH = GLES31.glGetUniformLocation( blitDepthProgramH, "u_DepthTexture");
+    mBlitDepthDepthH        = GLES31.glGetUniformLocation( blitDepthProgramH, "u_Depth");
+    mBlitDepthTexCorrH      = GLES31.glGetUniformLocation( blitDepthProgramH, "u_TexCorr");
+
+    // NORMAL PROGRAM //////////////////////////////////////
+    final InputStream normalVertexStream   = resources.openRawResource(R.raw.normal_vertex_shader);
+    final InputStream normalFragmentStream = resources.openRawResource(R.raw.normal_fragment_shader);
+
+    try
+      {
+      mNormalProgram = new DistortedProgram(normalVertexStream,normalFragmentStream, Distorted.GLSL_VERSION, Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile NORMAL program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int normalProgramH = mNormalProgram.getProgramHandle();
+    mNormalMVPMatrixH  = GLES31.glGetUniformLocation( normalProgramH, "u_MVPMatrix");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void createProgramsOIT(Resources resources)
+    {
+    // MAIN OIT PROGRAM ////////////////////////////////
+    final InputStream mainVertStream = resources.openRawResource(R.raw.main_vertex_shader);
+    final InputStream mainFragStream = resources.openRawResource(R.raw.main_fragment_shader);
+
+    int numF = FragmentEffect.getNumEnabled();
+    int numV = VertexEffect.getNumEnabled();
+
+    String mainVertHeader= Distorted.GLSL_VERSION +
+                           ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n") +
+                           ("#define OIT\n");
+    String mainFragHeader= Distorted.GLSL_VERSION +
+                           ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n") +
+                           ("#define OIT\n");
+
+    String enabledEffectV= VertexEffect.getGLSL();
+    String enabledEffectF= FragmentEffect.getGLSL();
+
+    try
+      {
+      mMainOITProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader, mainFragHeader,
+                                             enabledEffectV, enabledEffectF, Distorted.GLSL, null);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile MAIN OIT program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int mainOITProgramH = mMainOITProgram.getProgramHandle();
+    EffectQueue.getUniforms(mainOITProgramH,1);
+    mMainOITTextureH    = GLES31.glGetUniformLocation( mainOITProgramH, "u_Texture");
+    mMainOITSizeH       = GLES31.glGetUniformLocation( mainOITProgramH, "u_Size");
+    mMainOITNumRecordsH = GLES31.glGetUniformLocation( mainOITProgramH, "u_numRecords");
+
+    // OIT CLEAR PROGRAM ////////////////////////////////////
+    final InputStream oitClearVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
+    final InputStream oitClearFragStream = resources.openRawResource(R.raw.oit_clear_fragment_shader);
+
+    try
+      {
+      mOITClearProgram = new DistortedProgram(oitClearVertStream,oitClearFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT CLEAR program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitClearProgramH   = mOITClearProgram.getProgramHandle();
+    mOITClearDepthH        = GLES31.glGetUniformLocation( oitClearProgramH, "u_Depth");
+    mOITClearTexCorrH      = GLES31.glGetUniformLocation( oitClearProgramH, "u_TexCorr");
+    mOITClearSizeH         = GLES31.glGetUniformLocation( oitClearProgramH, "u_Size");
+
+    // OIT BUILD PROGRAM ////////////////////////////////////
+    final InputStream oitBuildVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
+    final InputStream oitBuildFragStream = resources.openRawResource(R.raw.oit_build_fragment_shader);
+
+    try
+      {
+      mOITBuildProgram = new DistortedProgram(oitBuildVertStream,oitBuildFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT BUILD program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitBuildProgramH   = mOITBuildProgram.getProgramHandle();
+    mOITBuildTextureH      = GLES31.glGetUniformLocation( oitBuildProgramH, "u_Texture");
+    mOITBuildDepthTextureH = GLES31.glGetUniformLocation( oitBuildProgramH, "u_DepthTexture");
+    mOITBuildDepthH        = GLES31.glGetUniformLocation( oitBuildProgramH, "u_Depth");
+    mOITBuildTexCorrH      = GLES31.glGetUniformLocation( oitBuildProgramH, "u_TexCorr");
+    mOITBuildSizeH         = GLES31.glGetUniformLocation( oitBuildProgramH, "u_Size");
+    mOITBuildNumRecordsH   = GLES31.glGetUniformLocation( oitBuildProgramH, "u_numRecords");
+
+    // OIT COLLAPSE PROGRAM ///////////////////////////
+    final InputStream oitCollapseVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
+    final InputStream oitCollapseFragStream = resources.openRawResource(R.raw.oit_collapse_fragment_shader);
+
+    try
+      {
+      mOITCollapseProgram = new DistortedProgram(oitCollapseVertStream,oitCollapseFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT COLLAPSE program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitCollapseProgramH   = mOITCollapseProgram.getProgramHandle();
+    mOITCollapseDepthTextureH = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_DepthTexture");
+    mOITCollapseDepthH        = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_Depth");
+    mOITCollapseTexCorrH      = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_TexCorr");
+    mOITCollapseSizeH         = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_Size");
+
+    // OIT RENDER PROGRAM ///////////////////////////
+    final InputStream oitRenderVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
+    final InputStream oitRenderFragStream = resources.openRawResource(R.raw.oit_render_fragment_shader);
+
+    try
+      {
+      mOITRenderProgram = new DistortedProgram(oitRenderVertStream,oitRenderFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
+      }
+    catch(Exception e)
+      {
+      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT RENDER program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitRenderProgramH   = mOITRenderProgram.getProgramHandle();
+    mOITRenderDepthH        = GLES31.glGetUniformLocation( oitRenderProgramH, "u_Depth");
+    mOITRenderTexCorrH      = GLES31.glGetUniformLocation( oitRenderProgramH, "u_TexCorr");
+    mOITRenderSizeH         = GLES31.glGetUniformLocation( oitRenderProgramH, "u_Size");
+    }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void displayNormals(EffectQueue[] queues, MeshBase mesh)
+    {
+    int num = mesh.getNumVertices();
+    int tfo = mesh.getTFO();
+
+    GLES31.glBindBufferBase(GLES31.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
+    GLES31.glBeginTransformFeedback( GLES31.GL_POINTS);
+    DistortedRenderState.switchOffDrawing();
+    GLES31.glDrawArrays( GLES31.GL_POINTS, 0, num );
+    DistortedRenderState.restoreDrawing();
+    GLES31.glEndTransformFeedback();
+    GLES31.glBindBufferBase(GLES31.GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+
+    Distorted.mNormalProgram.useProgram();
+    GLES31.glUniformMatrix4fv(Distorted.mNormalMVPMatrixH, 1, false, EffectQueue.getMVP(queues) , 0);
+    mesh.bindTransformAttribs(Distorted.mNormalProgram);
+    GLES31.glLineWidth(8.0f);
+    GLES31.glDrawArrays(GLES31.GL_LINES, 0, 2*num);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void drawPrivOIT(EffectQueue[] queues, float halfW, float halfH, MeshBase mesh, DistortedOutputSurface surface, long currTime)
+    {
+    float halfZ = halfW*mesh.getZFactor();
+
+    EffectQueue.compute(queues, currTime, halfW, halfH, halfZ );
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+
+    Distorted.mMainOITProgram.useProgram();
+    GLES31.glUniform1i(Distorted.mMainOITTextureH, 0);
+    GLES31.glUniform2ui(Distorted.mMainOITSizeH, surface.mWidth, surface.mHeight);
+    GLES31.glUniform1ui(Distorted.mMainOITNumRecordsH, (int)(Distorted.mBufferSize*surface.mWidth*surface.mHeight) );
+
+    mesh.bindVertexAttribs(Distorted.mMainOITProgram);
+
+    float inflate = mesh.getInflate();
+
+    EffectQueue.send(queues, surface, inflate, halfW, halfH, halfZ, 1 );
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
+
+    if( mesh.getShowNormals() )
+      {
+      Distorted.mMainProgram.useProgram();
+      EffectQueue.send(queues, surface, inflate, halfW, halfH, halfZ, 0 );
+      displayNormals(queues,mesh);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void drawPriv(EffectQueue[] queues, float halfW, float halfH, MeshBase mesh, DistortedOutputSurface surface, long currTime)
+    {
+    float halfZ = halfW*mesh.getZFactor();
+
+    EffectQueue.compute(queues, currTime, halfW, halfH, halfZ );
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    Distorted.mMainProgram.useProgram();
+    GLES31.glUniform1i(Distorted.mMainTextureH, 0);
+    mesh.bindVertexAttribs(Distorted.mMainProgram);
+    EffectQueue.send(queues, surface, mesh.getInflate(), halfW, halfH, halfZ, 0 );
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
+
+    if( mesh.getShowNormals() ) displayNormals(queues,mesh);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void blitPriv(DistortedOutputSurface surface)
+    {
+    mBlitProgram.useProgram();
+
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    GLES31.glUniform1i(mBlitTextureH, 0);
+    GLES31.glUniform1f( mBlitDepthH , 1.0f-surface.mNear);
+    GLES31.glVertexAttribPointer(mBlitProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void blitDepthPriv(DistortedOutputSurface surface, float corrW, float corrH)
+    {
+    mBlitDepthProgram.useProgram();
+
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    GLES31.glUniform1i(mBlitDepthTextureH, 0);
+    GLES31.glUniform1i(mBlitDepthDepthTextureH, 1);
+    GLES31.glUniform2f(mBlitDepthTexCorrH, corrW, corrH );
+    GLES31.glUniform1f( mBlitDepthDepthH , 1.0f-surface.mNear);
+    GLES31.glVertexAttribPointer(mBlitDepthProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int printPreviousBuffer()
+    {
+    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
+      {
+      android.util.Log.e("effects", "print: failed to map atomic buffer");
+      }
+
+    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);
+    if( atomicBuf!=null )
+      {
+      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
+      atomicIntBuf.put(0,0);
+      }
+    else
+      {
+      android.util.Log.e("effects", "zero: failed to map atomic buffer");
+      }
+
+    GLES31.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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, int counter)
+    {
+    if( mLinkedListSSBO[0]<0 )
+      {
+      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);
+      }
+
+    mOITClearProgram.useProgram();
+
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    GLES31.glUniform2f(mOITClearTexCorrH, 1.0f, 1.0f );   // corrections do not really matter here - only present because of common vertex shader.
+    GLES31.glUniform1f( mOITClearDepthH , 1.0f);          // likewise depth
+    GLES31.glUniform2ui(mOITClearSizeH, surface.mWidth, surface.mHeight);
+    GLES31.glVertexAttribPointer(mOITClearProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass2 of the OIT algorithm - build per-pixel linked lists.
+
+  static void oitBuild(DistortedOutputSurface surface, float corrW, float corrH)
+    {
+    mOITBuildProgram.useProgram();
+
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    GLES31.glUniform1i(mOITBuildTextureH, 0);
+    GLES31.glUniform1i(mOITBuildDepthTextureH, 1);
+    GLES31.glUniform2f(mOITBuildTexCorrH, corrW, corrH );
+    GLES31.glUniform2ui(mOITBuildSizeH, surface.mWidth, surface.mHeight);
+    GLES31.glUniform1ui(mOITBuildNumRecordsH, (int)(mBufferSize*surface.mWidth*surface.mHeight) );
+    GLES31.glUniform1f(mOITBuildDepthH , 1.0f-surface.mNear);
+    GLES31.glVertexAttribPointer(mOITBuildProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass3 of the OIT algorithm. Cut occluded parts of the linked list.
+
+  static void oitCollapse(DistortedOutputSurface surface, float corrW, float corrH)
+    {
+    mOITCollapseProgram.useProgram();
+
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    GLES31.glUniform1i(mOITCollapseDepthTextureH, 1);
+    GLES31.glUniform2f(mOITCollapseTexCorrH, corrW, corrH );
+    GLES31.glUniform2ui(mOITCollapseSizeH, surface.mWidth, surface.mHeight);
+    GLES31.glUniform1f( mOITCollapseDepthH , 1.0f-surface.mNear);
+    GLES31.glVertexAttribPointer(mOITCollapseProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass4 of the OIT algorithm. Render all the transparent pixels from the per-pixel linked lists.
+
+  static void oitRender(DistortedOutputSurface surface, float corrW, float corrH)
+    {
+    mOITRenderProgram.useProgram();
+
+    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
+    GLES31.glUniform2f(mOITRenderTexCorrH, corrW, corrH );
+    GLES31.glUniform2ui(mOITRenderSizeH, surface.mWidth, surface.mHeight);
+    GLES31.glUniform1f( mOITRenderDepthH , 1.0f-surface.mNear);
+    GLES31.glVertexAttribPointer(mOITRenderProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
+    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void setSSBOSize(float size)
+    {
+    mBufferSize = size;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // ARM Mali driver r12 has problems when we keep swapping many FBOs (fixed in r22)
 // PowerVR GE8100 compiler fails to compile OIT programs.
@@ -172,7 +749,7 @@ public class Distorted
 
     try
       {
-      DistortedEffects.createPrograms(resources);
+      createPrograms(resources);
       }
     catch(Exception ex)
       {
@@ -181,7 +758,7 @@ public class Distorted
 
     try
       {
-      DistortedEffects.createProgramsOIT(resources);
+      createProgramsOIT(resources);
       }
     catch(Exception ex)
       {
@@ -220,7 +797,10 @@ public class Distorted
   public static void onPause()
     {
     DistortedObject.onPause();
-    DistortedEffects.onPause();
+
+    mLinkedListSSBO[0]= -1;
+
+    for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++) mAtomicCounter[i] = -1;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -241,4 +821,41 @@ public class Distorted
 
     mInitialized = false;
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the maximum number of effects of a given type that can be simultaneously applied to a
+ * single (InputSurface,MeshBase) combo.
+ *
+ * @param type {@link EffectType}
+ * @return The maximum number of effects of a given type.
+ */
+  @SuppressWarnings("unused")
+  public static int getMax(EffectType type)
+    {
+    return EffectQueue.getMax(type.ordinal());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the maximum number of effects that can be stored in a single EffectQueue at one time.
+ * This can fail if:
+ * <ul>
+ * <li>the value of 'max' is outside permitted range (0 &le; max &le; Byte.MAX_VALUE)
+ * <li>We try to increase the value of 'max' when it is too late to do so already. It needs to be called
+ *     before the Vertex Shader gets compiled, i.e. before the call to {@link Distorted#onCreate}. After this
+ *     time only decreasing the value of 'max' is permitted.
+ * <li>Furthermore, this needs to be called before any instances of the DistortedEffects class get created.
+ * </ul>
+ *
+ * @param type {@link EffectType}
+ * @param max new maximum number of simultaneous effects. Has to be a non-negative number not greater
+ *            than Byte.MAX_VALUE
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  @SuppressWarnings("unused")
+  public static boolean setMax(EffectType type, int max)
+    {
+    return EffectQueue.setMax(type.ordinal(),max);
+    }
   }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/main/DistortedEffects.java b/src/main/java/org/distorted/library/main/DistortedEffects.java
index 3556d56..a2b69c0 100644
--- a/src/main/java/org/distorted/library/main/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/main/DistortedEffects.java
@@ -19,25 +19,10 @@
 
 package org.distorted.library.main;
 
-import android.content.res.Resources;
-import android.opengl.GLES31;
-import android.util.Log;
-
-import org.distorted.library.R;
 import org.distorted.library.effect.Effect;
 import org.distorted.library.effect.EffectName;
 import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.FragmentEffect;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.mesh.MeshBase;
 import org.distorted.library.message.EffectListener;
-import org.distorted.library.program.DistortedProgram;
-
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -47,300 +32,10 @@ import java.nio.IntBuffer;
  */
 public class DistortedEffects
   {
-  /// MAIN PROGRAM ///
-  private static DistortedProgram mMainProgram;
-  private static int mMainTextureH;
-
-  /// BLIT PROGRAM ///
-  private static DistortedProgram mBlitProgram;
-  private static int mBlitTextureH;
-  private static int mBlitDepthH;
-  private static final FloatBuffer mQuadPositions;
-
-  static
-    {
-    float[] positionData= { -0.5f, -0.5f,  -0.5f, 0.5f,  0.5f,-0.5f,  0.5f, 0.5f };
-    mQuadPositions = ByteBuffer.allocateDirect(32).order(ByteOrder.nativeOrder()).asFloatBuffer();
-    mQuadPositions.put(positionData).position(0);
-    }
-
-  /// BLIT DEPTH PROGRAM ///
-  private static DistortedProgram mBlitDepthProgram;
-  private static int mBlitDepthTextureH;
-  private static int mBlitDepthDepthTextureH;
-  private static int mBlitDepthDepthH;
-  private static int mBlitDepthTexCorrH;
-
-  /// NORMAL PROGRAM /////
-  private static DistortedProgram mNormalProgram;
-  private static int mNormalMVPMatrixH;
-
-  /// OIT SSBO BUFFER ///
-  private static int[] mLinkedListSSBO = new int[1];
-  private static int[] mAtomicCounter = new int[Distorted.FBO_QUEUE_SIZE];
-  private static int   mCurrBuffer;
-
-  static
-    {
-    mLinkedListSSBO[0]= -1;
-    mCurrBuffer       =  0;
-
-    for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++)  mAtomicCounter[i] = -1;
-    }
-
-  ///////////////////////////////////////////////////////////////
-  // meaning: allocate 1.0 screenful of places for transparent
-  // fragments in the SSBO backing up the OIT render method.
-  private static float mBufferSize=1.0f;
-
-  /// OIT CLEAR PROGRAM ///
-  private static DistortedProgram mOITClearProgram;
-  private static int mOITClearDepthH;
-  private static int mOITClearTexCorrH;
-  private static int mOITClearSizeH;
-
-  /// OIT BUILD PROGRAM ///
-  private static DistortedProgram mOITBuildProgram;
-  private static int mOITBuildTextureH;
-  private static int mOITBuildDepthTextureH;
-  private static int mOITBuildDepthH;
-  private static int mOITBuildTexCorrH;
-  private static int mOITBuildSizeH;
-  private static int mOITBuildNumRecordsH;
-
-  /// OIT COLLAPSE PROGRAM ///
-  private static DistortedProgram mOITCollapseProgram;
-  private static int mOITCollapseDepthTextureH;
-  private static int mOITCollapseDepthH;
-  private static int mOITCollapseTexCorrH;
-  private static int mOITCollapseSizeH;
-
-  /// OIT RENDER PROGRAM ///
-  private static DistortedProgram mOITRenderProgram;
-  private static int mOITRenderDepthH;
-  private static int mOITRenderTexCorrH;
-  private static int mOITRenderSizeH;
-
-  /// MAIN OIT PROGRAM ///
-  private static DistortedProgram mMainOITProgram;
-  private static int mMainOITTextureH;
-  private static int mMainOITSizeH;
-  private static int mMainOITNumRecordsH;
-  /// END PROGRAMS //////
-
   private static long mNextID =0;
   private long mID;
-
   private EffectQueue[] mQueues;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void createPrograms(Resources resources)
-    {
-    // MAIN PROGRAM ////////////////////////////////////
-    final InputStream mainVertStream = resources.openRawResource(R.raw.main_vertex_shader);
-    final InputStream mainFragStream = resources.openRawResource(R.raw.main_fragment_shader);
-
-    int numF = FragmentEffect.getNumEnabled();
-    int numV = VertexEffect.getNumEnabled();
-
-    String mainVertHeader= Distorted.GLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n");
-    String mainFragHeader= Distorted.GLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n");
-    String enabledEffectV= VertexEffect.getGLSL();
-    String enabledEffectF= FragmentEffect.getGLSL();
-
-    String[] feedback = { "v_Position", "v_endPosition" };
-
-    try
-      {
-      mMainProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader, mainFragHeader,
-                                          enabledEffectV, enabledEffectF, Distorted.GLSL, feedback);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile MAIN program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int mainProgramH = mMainProgram.getProgramHandle();
-    EffectQueue.getUniforms(mainProgramH,0);
-    mMainTextureH= GLES31.glGetUniformLocation( mainProgramH, "u_Texture");
-
-    // BLIT PROGRAM ////////////////////////////////////
-    final InputStream blitVertStream = resources.openRawResource(R.raw.blit_vertex_shader);
-    final InputStream blitFragStream = resources.openRawResource(R.raw.blit_fragment_shader);
-
-    try
-      {
-      mBlitProgram = new DistortedProgram(blitVertStream,blitFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile BLIT program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int blitProgramH = mBlitProgram.getProgramHandle();
-    mBlitTextureH  = GLES31.glGetUniformLocation( blitProgramH, "u_Texture");
-    mBlitDepthH    = GLES31.glGetUniformLocation( blitProgramH, "u_Depth");
-
-    // BLIT DEPTH PROGRAM ////////////////////////////////////
-    final InputStream blitDepthVertStream = resources.openRawResource(R.raw.blit_depth_vertex_shader);
-    final InputStream blitDepthFragStream = resources.openRawResource(R.raw.blit_depth_fragment_shader);
-
-    try
-      {
-      mBlitDepthProgram = new DistortedProgram(blitDepthVertStream,blitDepthFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile BLIT DEPTH program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int blitDepthProgramH   = mBlitDepthProgram.getProgramHandle();
-    mBlitDepthTextureH      = GLES31.glGetUniformLocation( blitDepthProgramH, "u_Texture");
-    mBlitDepthDepthTextureH = GLES31.glGetUniformLocation( blitDepthProgramH, "u_DepthTexture");
-    mBlitDepthDepthH        = GLES31.glGetUniformLocation( blitDepthProgramH, "u_Depth");
-    mBlitDepthTexCorrH      = GLES31.glGetUniformLocation( blitDepthProgramH, "u_TexCorr");
-
-    // NORMAL PROGRAM //////////////////////////////////////
-    final InputStream normalVertexStream   = resources.openRawResource(R.raw.normal_vertex_shader);
-    final InputStream normalFragmentStream = resources.openRawResource(R.raw.normal_fragment_shader);
-
-    try
-      {
-      mNormalProgram = new DistortedProgram(normalVertexStream,normalFragmentStream, Distorted.GLSL_VERSION, Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile NORMAL program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int normalProgramH = mNormalProgram.getProgramHandle();
-    mNormalMVPMatrixH  = GLES31.glGetUniformLocation( normalProgramH, "u_MVPMatrix");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void createProgramsOIT(Resources resources)
-    {
-    // MAIN OIT PROGRAM ////////////////////////////////
-    final InputStream mainVertStream = resources.openRawResource(R.raw.main_vertex_shader);
-    final InputStream mainFragStream = resources.openRawResource(R.raw.main_fragment_shader);
-
-    int numF = FragmentEffect.getNumEnabled();
-    int numV = VertexEffect.getNumEnabled();
-
-    String mainVertHeader= Distorted.GLSL_VERSION +
-                           ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n") +
-                           ("#define OIT\n");
-    String mainFragHeader= Distorted.GLSL_VERSION +
-                           ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n") +
-                           ("#define OIT\n");
-
-    String enabledEffectV= VertexEffect.getGLSL();
-    String enabledEffectF= FragmentEffect.getGLSL();
-
-    try
-      {
-      mMainOITProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader, mainFragHeader,
-                                             enabledEffectV, enabledEffectF, Distorted.GLSL, null);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile MAIN OIT program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int mainOITProgramH = mMainOITProgram.getProgramHandle();
-    EffectQueue.getUniforms(mainOITProgramH,1);
-    mMainOITTextureH    = GLES31.glGetUniformLocation( mainOITProgramH, "u_Texture");
-    mMainOITSizeH       = GLES31.glGetUniformLocation( mainOITProgramH, "u_Size");
-    mMainOITNumRecordsH = GLES31.glGetUniformLocation( mainOITProgramH, "u_numRecords");
-
-    // OIT CLEAR PROGRAM ////////////////////////////////////
-    final InputStream oitClearVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
-    final InputStream oitClearFragStream = resources.openRawResource(R.raw.oit_clear_fragment_shader);
-
-    try
-      {
-      mOITClearProgram = new DistortedProgram(oitClearVertStream,oitClearFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT CLEAR program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitClearProgramH   = mOITClearProgram.getProgramHandle();
-    mOITClearDepthH        = GLES31.glGetUniformLocation( oitClearProgramH, "u_Depth");
-    mOITClearTexCorrH      = GLES31.glGetUniformLocation( oitClearProgramH, "u_TexCorr");
-    mOITClearSizeH         = GLES31.glGetUniformLocation( oitClearProgramH, "u_Size");
-
-    // OIT BUILD PROGRAM ////////////////////////////////////
-    final InputStream oitBuildVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
-    final InputStream oitBuildFragStream = resources.openRawResource(R.raw.oit_build_fragment_shader);
-
-    try
-      {
-      mOITBuildProgram = new DistortedProgram(oitBuildVertStream,oitBuildFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT BUILD program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitBuildProgramH   = mOITBuildProgram.getProgramHandle();
-    mOITBuildTextureH      = GLES31.glGetUniformLocation( oitBuildProgramH, "u_Texture");
-    mOITBuildDepthTextureH = GLES31.glGetUniformLocation( oitBuildProgramH, "u_DepthTexture");
-    mOITBuildDepthH        = GLES31.glGetUniformLocation( oitBuildProgramH, "u_Depth");
-    mOITBuildTexCorrH      = GLES31.glGetUniformLocation( oitBuildProgramH, "u_TexCorr");
-    mOITBuildSizeH         = GLES31.glGetUniformLocation( oitBuildProgramH, "u_Size");
-    mOITBuildNumRecordsH   = GLES31.glGetUniformLocation( oitBuildProgramH, "u_numRecords");
-
-    // OIT COLLAPSE PROGRAM ///////////////////////////
-    final InputStream oitCollapseVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
-    final InputStream oitCollapseFragStream = resources.openRawResource(R.raw.oit_collapse_fragment_shader);
-
-    try
-      {
-      mOITCollapseProgram = new DistortedProgram(oitCollapseVertStream,oitCollapseFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT COLLAPSE program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitCollapseProgramH   = mOITCollapseProgram.getProgramHandle();
-    mOITCollapseDepthTextureH = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_DepthTexture");
-    mOITCollapseDepthH        = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_Depth");
-    mOITCollapseTexCorrH      = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_TexCorr");
-    mOITCollapseSizeH         = GLES31.glGetUniformLocation( oitCollapseProgramH, "u_Size");
-
-    // OIT RENDER PROGRAM ///////////////////////////
-    final InputStream oitRenderVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
-    final InputStream oitRenderFragStream = resources.openRawResource(R.raw.oit_render_fragment_shader);
-
-    try
-      {
-      mOITRenderProgram = new DistortedProgram(oitRenderVertStream,oitRenderFragStream,Distorted.GLSL_VERSION,Distorted.GLSL_VERSION, Distorted.GLSL);
-      }
-    catch(Exception e)
-      {
-      Log.e("EFFECTS", e.getClass().getSimpleName()+" trying to compile OIT RENDER program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitRenderProgramH   = mOITRenderProgram.getProgramHandle();
-    mOITRenderDepthH        = GLES31.glGetUniformLocation( oitRenderProgramH, "u_Depth");
-    mOITRenderTexCorrH      = GLES31.glGetUniformLocation( oitRenderProgramH, "u_TexCorr");
-    mOITRenderSizeH         = GLES31.glGetUniformLocation( oitRenderProgramH, "u_Size");
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   EffectQueue[] getQueues()
@@ -362,280 +57,6 @@ public class DistortedEffects
     EffectQueue.removeNode(mQueues,node);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void displayNormals(MeshBase mesh)
-    {
-    int num = mesh.getNumVertices();
-    int tfo = mesh.getTFO();
-
-    GLES31.glBindBufferBase(GLES31.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
-    GLES31.glBeginTransformFeedback( GLES31.GL_POINTS);
-    DistortedRenderState.switchOffDrawing();
-    GLES31.glDrawArrays( GLES31.GL_POINTS, 0, num );
-    DistortedRenderState.restoreDrawing();
-    GLES31.glEndTransformFeedback();
-    GLES31.glBindBufferBase(GLES31.GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
-
-    mNormalProgram.useProgram();
-    GLES31.glUniformMatrix4fv(mNormalMVPMatrixH, 1, false, EffectQueue.getMVP(mQueues) , 0);
-    mesh.bindTransformAttribs(mNormalProgram);
-    GLES31.glLineWidth(8.0f);
-    GLES31.glDrawArrays(GLES31.GL_LINES, 0, 2*num);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void drawPrivOIT(float halfW, float halfH, MeshBase mesh, DistortedOutputSurface surface, long currTime)
-    {
-    float halfZ = halfW*mesh.getZFactor();
-
-    EffectQueue.compute(mQueues, currTime, halfW, halfH, halfZ );
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-
-    mMainOITProgram.useProgram();
-    GLES31.glUniform1i(mMainOITTextureH, 0);
-    GLES31.glUniform2ui(mMainOITSizeH, surface.mWidth, surface.mHeight);
-    GLES31.glUniform1ui(mMainOITNumRecordsH, (int)(mBufferSize*surface.mWidth*surface.mHeight) );
-
-    mesh.bindVertexAttribs(mMainOITProgram);
-
-    float inflate = mesh.getInflate();
-
-    EffectQueue.send(mQueues, surface, inflate, halfW, halfH, halfZ, 1 );
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
-
-    if( mesh.getShowNormals() )
-      {
-      mMainProgram.useProgram();
-      EffectQueue.send(mQueues, surface, inflate, halfW, halfH, halfZ, 0 );
-      displayNormals(mesh);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void drawPriv(float halfW, float halfH, MeshBase mesh, DistortedOutputSurface surface, long currTime)
-    {
-    float halfZ = halfW*mesh.getZFactor();
-
-    EffectQueue.compute(mQueues, currTime, halfW, halfH, halfZ );
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    mMainProgram.useProgram();
-    GLES31.glUniform1i(mMainTextureH, 0);
-    mesh.bindVertexAttribs(mMainProgram);
-    EffectQueue.send(mQueues, surface, mesh.getInflate(), halfW, halfH, halfZ, 0 );
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
-
-    if( mesh.getShowNormals() ) displayNormals(mesh);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void blitPriv(DistortedOutputSurface surface)
-    {
-    mBlitProgram.useProgram();
-
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    GLES31.glUniform1i(mBlitTextureH, 0);
-    GLES31.glUniform1f( mBlitDepthH , 1.0f-surface.mNear);
-    GLES31.glVertexAttribPointer(mBlitProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void blitDepthPriv(DistortedOutputSurface surface, float corrW, float corrH)
-    {
-    mBlitDepthProgram.useProgram();
-
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    GLES31.glUniform1i(mBlitDepthTextureH, 0);
-    GLES31.glUniform1i(mBlitDepthDepthTextureH, 1);
-    GLES31.glUniform2f(mBlitDepthTexCorrH, corrW, corrH );
-    GLES31.glUniform1f( mBlitDepthDepthH , 1.0f-surface.mNear);
-    GLES31.glVertexAttribPointer(mBlitDepthProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int printPreviousBuffer()
-    {
-    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
-      {
-      android.util.Log.e("effects", "print: failed to map atomic buffer");
-      }
-
-    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);
-    if( atomicBuf!=null )
-      {
-      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
-      atomicIntBuf.put(0,0);
-      }
-    else
-      {
-      android.util.Log.e("effects", "zero: failed to map atomic buffer");
-      }
-
-    GLES31.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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, int counter)
-    {
-    if( mLinkedListSSBO[0]<0 )
-      {
-      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);
-      }
-
-    mOITClearProgram.useProgram();
-
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    GLES31.glUniform2f(mOITClearTexCorrH, 1.0f, 1.0f );   // corrections do not really matter here - only present because of common vertex shader.
-    GLES31.glUniform1f( mOITClearDepthH , 1.0f);          // likewise depth
-    GLES31.glUniform2ui(mOITClearSizeH, surface.mWidth, surface.mHeight);
-    GLES31.glVertexAttribPointer(mOITClearProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass2 of the OIT algorithm - build per-pixel linked lists.
-
-  static void oitBuild(DistortedOutputSurface surface, float corrW, float corrH)
-    {
-    mOITBuildProgram.useProgram();
-
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    GLES31.glUniform1i(mOITBuildTextureH, 0);
-    GLES31.glUniform1i(mOITBuildDepthTextureH, 1);
-    GLES31.glUniform2f(mOITBuildTexCorrH, corrW, corrH );
-    GLES31.glUniform2ui(mOITBuildSizeH, surface.mWidth, surface.mHeight);
-    GLES31.glUniform1ui(mOITBuildNumRecordsH, (int)(mBufferSize*surface.mWidth*surface.mHeight) );
-    GLES31.glUniform1f(mOITBuildDepthH , 1.0f-surface.mNear);
-    GLES31.glVertexAttribPointer(mOITBuildProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass3 of the OIT algorithm. Cut occluded parts of the linked list.
-
-  static void oitCollapse(DistortedOutputSurface surface, float corrW, float corrH)
-    {
-    mOITCollapseProgram.useProgram();
-
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    GLES31.glUniform1i(mOITCollapseDepthTextureH, 1);
-    GLES31.glUniform2f(mOITCollapseTexCorrH, corrW, corrH );
-    GLES31.glUniform2ui(mOITCollapseSizeH, surface.mWidth, surface.mHeight);
-    GLES31.glUniform1f( mOITCollapseDepthH , 1.0f-surface.mNear);
-    GLES31.glVertexAttribPointer(mOITCollapseProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass4 of the OIT algorithm. Render all the transparent pixels from the per-pixel linked lists.
-
-  static void oitRender(DistortedOutputSurface surface, float corrW, float corrH)
-    {
-    mOITRenderProgram.useProgram();
-
-    GLES31.glViewport(0, 0, surface.mWidth, surface.mHeight );
-    GLES31.glUniform2f(mOITRenderTexCorrH, corrW, corrH );
-    GLES31.glUniform2ui(mOITRenderSizeH, surface.mWidth, surface.mHeight);
-    GLES31.glUniform1f( mOITRenderDepthH , 1.0f-surface.mNear);
-    GLES31.glVertexAttribPointer(mOITRenderProgram.mAttribute[0], 2, GLES31.GL_FLOAT, false, 0, mQuadPositions);
-    GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onPause()
-    {
-    mLinkedListSSBO[0]= -1;
-
-    for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++) mAtomicCounter[i] = -1;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   static void onDestroy()
@@ -643,13 +64,6 @@ public class DistortedEffects
     mNextID =  0;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void setSSBOSize(float size)
-    {
-    mBufferSize = size;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -791,43 +205,6 @@ public class DistortedEffects
     return mQueues[num].removeByName(name);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the maximum number of effects of a given type that can be simultaneously applied to a
- * single (InputSurface,MeshBase) combo.
- *
- * @param type {@link EffectType}
- * @return The maximum number of effects of a given type.
- */
-  @SuppressWarnings("unused")
-  public static int getMax(EffectType type)
-    {
-    return EffectQueue.getMax(type.ordinal());
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets the maximum number of effects that can be stored in a single EffectQueue at one time.
- * This can fail if:
- * <ul>
- * <li>the value of 'max' is outside permitted range (0 &le; max &le; Byte.MAX_VALUE)
- * <li>We try to increase the value of 'max' when it is too late to do so already. It needs to be called
- *     before the Vertex Shader gets compiled, i.e. before the call to {@link Distorted#onCreate}. After this
- *     time only decreasing the value of 'max' is permitted.
- * <li>Furthermore, this needs to be called before any instances of the DistortedEffects class get created.
- * </ul>
- *
- * @param type {@link EffectType}
- * @param max new maximum number of simultaneous effects. Has to be a non-negative number not greater
- *            than Byte.MAX_VALUE
- * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
- */
-  @SuppressWarnings("unused")
-  public static boolean setMax(EffectType type, int max)
-    {
-    return EffectQueue.setMax(type.ordinal(),max);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Add a new Effect to our queue.
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.java b/src/main/java/org/distorted/library/main/DistortedNode.java
index d376dfe..1272633 100644
--- a/src/main/java/org/distorted/library/main/DistortedNode.java
+++ b/src/main/java/org/distorted/library/main/DistortedNode.java
@@ -163,7 +163,7 @@ public class DistortedNode implements DistortedChildrenList.Parent
       {
       mState.apply();
       GLES31.glDisable(GLES31.GL_BLEND);
-      mEffects.drawPriv(mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
+      Distorted.drawPriv(mEffects.getQueues(), mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
       GLES31.glEnable(GLES31.GL_BLEND);
       return 1;
       }
@@ -181,7 +181,7 @@ public class DistortedNode implements DistortedChildrenList.Parent
     if( input.setAsInput() )
       {
       mState.apply();
-      mEffects.drawPrivOIT(mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
+      Distorted.drawPrivOIT(mEffects.getQueues(), mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
       return 1;
       }
 
@@ -198,7 +198,7 @@ public class DistortedNode implements DistortedChildrenList.Parent
     if( input.setAsInput() )
       {
       mState.apply();
-      mEffects.drawPriv(mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
+      Distorted.drawPriv(mEffects.getQueues(), mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
       return 1;
       }
 
@@ -226,7 +226,7 @@ public class DistortedNode implements DistortedChildrenList.Parent
       if( mSurface.setAsInput() )
         {
         numRenders++;
-        DistortedEffects.blitPriv(mData.mFBO);
+        Distorted.blitPriv(mData.mFBO);
         }
 
       numRenders += mData.mFBO.renderChildren(currTime,numChildren,mChildren,0, mRenderWayOIT);
@@ -389,7 +389,7 @@ public class DistortedNode implements DistortedChildrenList.Parent
     mRenderWayOIT = oit;
 
     if( initialSize>0.0f && initialSize<10.0f )
-      DistortedEffects.setSSBOSize(initialSize);
+      Distorted.setSSBOSize(initialSize);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
index eeace94..d1da899 100644
--- a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
@@ -263,7 +263,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     GLES31.glDisable(GLES31.GL_STENCIL_TEST);
     GLES31.glStencilMask(0x00);
 
-    DistortedEffects.blitDepthPriv(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
+    Distorted.blitDepthPriv(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
     GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
     GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);
     GLES31.glActiveTexture(GLES31.GL_TEXTURE1);
@@ -290,8 +290,8 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
   private static void oitClear(DistortedOutputSurface buffer)
     {
-    int counter = DistortedEffects.zeroOutAtomic();
-    DistortedEffects.oitClear(buffer,counter);
+    int counter = Distorted.zeroOutAtomic();
+    Distorted.oitClear(buffer,counter);
     GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT|GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
     }
 
@@ -309,7 +309,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     DistortedRenderState.colorDepthStencilOn();
     DistortedRenderState.enableDepthTest();
 
-    DistortedEffects.oitBuild(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
+    Distorted.oitBuild(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
     GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
     GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);
     GLES31.glActiveTexture(GLES31.GL_TEXTURE1);
@@ -338,13 +338,13 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
       GLES31.glActiveTexture(GLES31.GL_TEXTURE1);
       GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mDepthStencilH[fbo]);
       DistortedRenderState.switchOffColorDepthStencil();
-      DistortedEffects.oitCollapse(this, corrW, corrH);
+      Distorted.oitCollapse(this, corrW, corrH);
       GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);
       }
 
     setAsOutput(currTime);
     DistortedRenderState.switchColorDepthOnStencilOff();
-    DistortedEffects.oitRender(this, corrW, corrH);
+    Distorted.oitRender(this, corrW, corrH);
     DistortedRenderState.restoreColorDepthStencil();
 
     return 1;
@@ -866,7 +866,7 @@ public void setOrderIndependentTransparency(boolean oit, float initialSize)
   mRenderWayOIT = oit;
 
   if( initialSize>0.0f && initialSize<10.0f )
-    DistortedEffects.setSSBOSize(initialSize);
+    Distorted.setSSBOSize(initialSize);
   }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/DistortedScreen.java b/src/main/java/org/distorted/library/main/DistortedScreen.java
index 6113391..7754a58 100644
--- a/src/main/java/org/distorted/library/main/DistortedScreen.java
+++ b/src/main/java/org/distorted/library/main/DistortedScreen.java
@@ -129,11 +129,11 @@ public class DistortedScreen extends DistortedFramebuffer
     GLES31.glDisable(GLES31.GL_DEPTH_TEST);
     GLES31.glDisable(GLES31.GL_BLEND);
 
-    DistortedEffects.blitPriv(this);
+    Distorted.blitPriv(this);
 
     if( mShowFPS && fpsTexture.setAsInput())
       {
-      fpsEffects.drawPriv(FPS_W / 2.0f, FPS_H / 2.0f, fpsMesh, this, time);
+      Distorted.drawPriv(fpsEffects.getQueues(), FPS_W / 2.0f, FPS_H / 2.0f, fpsMesh, this, time);
       }
 
     if( ++mCurRenderedFBO>=Distorted.FBO_QUEUE_SIZE )
diff --git a/src/main/java/org/distorted/library/main/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/main/EffectQueuePostprocess.java
index 7f85e1d..9a9578a 100644
--- a/src/main/java/org/distorted/library/main/EffectQueuePostprocess.java
+++ b/src/main/java/org/distorted/library/main/EffectQueuePostprocess.java
@@ -107,7 +107,7 @@ class EffectQueuePostprocess extends EffectQueue
 
     int numV = VertexEffect.getNumEnabled();
 
-    String mainVertHeader= Distorted.GLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? DistortedEffects.getMax(EffectType.VERTEX  ) : 0 ) + "\n");
+    String mainVertHeader= Distorted.GLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? Distorted.getMax(EffectType.VERTEX  ) : 0 ) + "\n");
     String mainFragHeader= Distorted.GLSL_VERSION + "\n";
 
     String enabledEffectV= VertexEffect.getGLSL();
