commit 0bd9f6449b59699922e8df2c7533129eae05b33f
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Aug 19 14:31:38 2020 +0100

    Introduce an UBO to the vertex shader holding info about mesh effect associations.

diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
index 848ca71..4ccf422 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
@@ -56,6 +56,7 @@ public class EffectQueuePostprocess extends EffectQueue
   private static DistortedProgram mPreProgram;
   private static int mPreColorH;
   private static int mPreTextureH;
+  private static int mPreProgramH;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -139,12 +140,11 @@ public class EffectQueuePostprocess extends EffectQueue
       throw new RuntimeException(e.getMessage());
       }
 
-    int preProgramH = mPreProgram.getProgramHandle();
-    EffectQueueVertex.getUniforms( preProgramH,2 );
-    EffectQueueMatrix.getUniforms( preProgramH,2 );
-    MeshBase.getUniforms(preProgramH,2);
-    mPreColorH  = GLES30.glGetUniformLocation( preProgramH, "u_Color"  );
-    mPreTextureH= GLES30.glGetUniformLocation( preProgramH, "u_Texture");
+    mPreProgramH = mPreProgram.getProgramHandle();
+    EffectQueueVertex.getUniforms( mPreProgramH,2 );
+    EffectQueueMatrix.getUniforms( mPreProgramH,2 );
+    mPreColorH  = GLES30.glGetUniformLocation( mPreProgramH, "u_Color"  );
+    mPreTextureH= GLES30.glGetUniformLocation( mPreProgramH, "u_Texture");
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -181,7 +181,7 @@ public class EffectQueuePostprocess extends EffectQueue
     mPreProgram.useProgram();
 
     mesh.bindVertexAttribs(mPreProgram);
-    mesh.send(2);
+    mesh.send(mPreProgramH);
 
     EffectQueue[] queues = effects.getQueues();
     EffectQueueMatrix matrix = (EffectQueueMatrix)queues[0];
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.java b/src/main/java/org/distorted/library/main/DistortedLibrary.java
index 0e89ead..3bbdd4b 100644
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.java
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.java
@@ -158,6 +158,9 @@ public class DistortedLibrary
   private static int mBlitDepthDepthH;
   private static int mBlitDepthTexCorrH;
 
+  /// Program Handles ///
+  private static int mMainProgramH, mFullProgramH, mMainOITProgramH;
+
   /// OIT SSBO BUFFER ///
   private static int[] mLinkedListSSBO = new int[1];
   private static int[] mAtomicCounter;
@@ -259,11 +262,10 @@ public class DistortedLibrary
       throw new RuntimeException(e.getMessage());
       }
 
-    int mainProgramH = mMainProgram.getProgramHandle();
-    EffectQueue.getUniforms(mainProgramH,0);
-    MeshBase.getUniforms(mainProgramH,0);
-    mMainTextureH= GLES30.glGetUniformLocation( mainProgramH, "u_Texture");
-    mTransformFeedbackH= GLES30.glGetUniformLocation( mainProgramH, "u_TransformFeedback");
+    mMainProgramH = mMainProgram.getProgramHandle();
+    EffectQueue.getUniforms(mMainProgramH,0);
+    mMainTextureH= GLES30.glGetUniformLocation( mMainProgramH, "u_Texture");
+    mTransformFeedbackH= GLES30.glGetUniformLocation( mMainProgramH, "u_TransformFeedback");
 
     // BLIT PROGRAM ////////////////////////////////////
     final InputStream blitVertStream = mResources.openRawResource(R.raw.blit_vertex_shader);
@@ -302,6 +304,11 @@ public class DistortedLibrary
     mBlitDepthDepthTextureH = GLES30.glGetUniformLocation( blitDepthProgramH, "u_DepthTexture");
     mBlitDepthDepthH        = GLES30.glGetUniformLocation( blitDepthProgramH, "u_Depth");
     mBlitDepthTexCorrH      = GLES30.glGetUniformLocation( blitDepthProgramH, "u_TexCorr");
+
+    int[] params = new int[1];
+    int index = GLES30.glGetUniformBlockIndex(mMainProgramH, "meshAssociation");
+    GLES30.glGetActiveUniformBlockiv( mMainProgramH, index, GLES30.GL_UNIFORM_BLOCK_DATA_SIZE, params, 0);
+    MeshBase.setAssociationSize(params[0]);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -358,9 +365,8 @@ public class DistortedLibrary
       throw new RuntimeException(e.getMessage());
       }
 
-    int fullProgramH = mFullProgram.getProgramHandle();
-    EffectQueue.getUniforms(fullProgramH,3);
-    MeshBase.getUniforms(fullProgramH,3);
+    mFullProgramH = mFullProgram.getProgramHandle();
+    EffectQueue.getUniforms(mFullProgramH,3);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -393,12 +399,11 @@ public class DistortedLibrary
       throw new RuntimeException(e.getMessage());
       }
 
-    int mainOITProgramH = mMainOITProgram.getProgramHandle();
-    EffectQueue.getUniforms(mainOITProgramH,1);
-    MeshBase.getUniforms(mainOITProgramH,1);
-    mMainOITTextureH    = GLES30.glGetUniformLocation( mainOITProgramH, "u_Texture");
-    mMainOITSizeH       = GLES30.glGetUniformLocation( mainOITProgramH, "u_Size");
-    mMainOITNumRecordsH = GLES30.glGetUniformLocation( mainOITProgramH, "u_numRecords");
+    mMainOITProgramH = mMainOITProgram.getProgramHandle();
+    EffectQueue.getUniforms(mMainOITProgramH,1);
+    mMainOITTextureH    = GLES30.glGetUniformLocation( mMainOITProgramH, "u_Texture");
+    mMainOITSizeH       = GLES30.glGetUniformLocation( mMainOITProgramH, "u_Size");
+    mMainOITNumRecordsH = GLES30.glGetUniformLocation( mMainOITProgramH, "u_numRecords");
 
     // OIT CLEAR PROGRAM ////////////////////////////////////
     final InputStream oitClearVertStream = resources.openRawResource(R.raw.oit_vertex_shader);
@@ -518,8 +523,11 @@ public class DistortedLibrary
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// execute all VertexEffects and adjust all vertices
-
+/**
+ * Execute all VertexEffects and adjust all vertices
+ *
+ * @y.exclude
+ */
   public static void adjustVertices(MeshBase mesh, EffectQueueVertex queue)
     {
     if( mFullProgram==null )
@@ -542,7 +550,7 @@ public class DistortedLibrary
     mesh.bindVertexAttribs(mFullProgram);
     queue.compute(1);
     queue.send(0.0f,3);
-    mesh.send(3);
+    mesh.send(mFullProgramH);
 
     GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
     GLES30.glBeginTransformFeedback( GLES30.GL_POINTS);
@@ -570,7 +578,7 @@ public class DistortedLibrary
       GLES30.glUniform2ui(mMainOITSizeH, surface.mWidth, surface.mHeight);
       GLES30.glUniform1ui(mMainOITNumRecordsH, (int)(mBufferSize*surface.mWidth*surface.mHeight) );
       mesh.bindVertexAttribs(mMainOITProgram);
-      mesh.send(1);
+      mesh.send(mMainOITProgramH);
 
       float inflate     = mesh.getInflate();
       float distance    = surface.mDistance;
@@ -583,7 +591,7 @@ public class DistortedLibrary
       if( mesh.getShowNormals() )
         {
         mMainProgram.useProgram();
-        mesh.send(0);
+        mesh.send(mMainProgramH);
         EffectQueue.send(queues, distance, mipmap, projection, inflate, 0 );
         displayNormals(projection,mesh);
         }
@@ -604,7 +612,7 @@ public class DistortedLibrary
       mMainProgram.useProgram();
       GLES30.glUniform1i(DistortedLibrary.mMainTextureH, 0);
       mesh.bindVertexAttribs(DistortedLibrary.mMainProgram);
-      mesh.send(0);
+      mesh.send(mMainProgramH);
 
       float inflate     = mesh.getInflate();
       float distance    = surface.mDistance;
diff --git a/src/main/java/org/distorted/library/main/InternalBuffer.java b/src/main/java/org/distorted/library/main/InternalBuffer.java
index c4dacad..72057c3 100644
--- a/src/main/java/org/distorted/library/main/InternalBuffer.java
+++ b/src/main/java/org/distorted/library/main/InternalBuffer.java
@@ -20,9 +20,12 @@
 package org.distorted.library.main;
 
 import android.opengl.GLES30;
+
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -37,7 +40,7 @@ public class InternalBuffer extends InternalObject
   {
   private final int[] mIndex;
   private int mTarget, mSize, mUsage;
-  private FloatBuffer mBuffer;
+  private Buffer mBuffer;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -57,7 +60,7 @@ public class InternalBuffer extends InternalObject
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // must be called from a thread holding OpenGL Context.
 
-  public int createImmediately(int size, float[] buffer)
+  public int createImmediatelyFloat(int size, float[] buffer)
     {
     if( mIndex[0]<0 )
       {
@@ -85,12 +88,55 @@ public class InternalBuffer extends InternalObject
     return mIndex[0];
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context.
+
+  public int createImmediatelyInt(int size, int[] buffer)
+    {
+    if( mIndex[0]<0 )
+      {
+      mSize= size;
+
+      if( buffer!=null )
+        {
+        IntBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asIntBuffer();
+        buf.put(buffer).position(0);
+        mBuffer = buf;
+        }
+      else
+        {
+        mBuffer = null;
+        }
+
+      GLES30.glGenBuffers( 1, mIndex, 0);
+      GLES30.glBindBuffer( mTarget,  mIndex[0]);
+      GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
+      GLES30.glBindBuffer( mTarget,  0);
+
+      markWasCreatedImmediately();
+      }
+
+    return mIndex[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// buffer non-null!!
+
+  public void updateFloat(float[] buffer)
+    {
+    ((FloatBuffer)mBuffer).put(buffer).position(0);
+
+    GLES30.glBindBuffer( mTarget, mIndex[0]);
+    GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
+    GLES30.glBindBuffer( mTarget, 0);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // buffer non-null!!
 
-  public void update(float[] buffer)
+  public void updateInt(int[] buffer)
     {
-    mBuffer.put(buffer).position(0);
+    ((IntBuffer)mBuffer).put(buffer).position(0);
 
     GLES30.glBindBuffer( mTarget, mIndex[0]);
     GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
diff --git a/src/main/java/org/distorted/library/mesh/MeshBase.java b/src/main/java/org/distorted/library/mesh/MeshBase.java
index 2ebfac0..1886b7d 100644
--- a/src/main/java/org/distorted/library/mesh/MeshBase.java
+++ b/src/main/java/org/distorted/library/mesh/MeshBase.java
@@ -25,7 +25,6 @@ import android.util.Log;
 
 import org.distorted.library.effect.MatrixEffect;
 import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.effectqueue.EffectQueue;
 import org.distorted.library.main.InternalBuffer;
 import org.distorted.library.program.DistortedProgram;
 import org.distorted.library.type.Static4D;
@@ -47,6 +46,7 @@ import java.util.ArrayList;
  */
 public abstract class MeshBase
    {
+   private static final int UBO_BINDING = 1;
    private static final int MAX_EFFECT_COMPONENTS= 100;
    private static final int DEFAULT_ASSOCIATION = 0xffffffff;
 
@@ -79,20 +79,21 @@ public abstract class MeshBase
    private static final int VERT1_SIZE = VERT1_ATTRIBS*BYTES_PER_FLOAT;
    private static final int VERT2_SIZE = VERT2_ATTRIBS*BYTES_PER_FLOAT;
 
+   private static int mAssociationSize = 8*MAX_EFFECT_COMPONENTS;
+
    private boolean mShowNormals;              // when rendering this mesh, draw normal vectors?
    private InternalBuffer mVBO1, mVBO2, mTFO; // main vertex buffer and transform feedback buffer
+   private InternalBuffer mUBO;               // Uniform Buffer Object
    private int mNumVertices;
    private float[] mVertAttribs1;             // packed: PosX,PosY,PosZ, NorX,NorY,NorZ, InfX,InfY,InfZ
    private float[] mVertAttribs2;             // packed: TexS,TexT, Component
    private float mInflate;
-   private int[] mEquAssociation;
-   private int[] mAndAssociation;
+   private int[] mAssociations;
+   private int mAssociationBlock;
+   private boolean mNeedAdjustAssociation;
 
    DeferredJobs.JobNode[] mJobNode;
 
-   private static int[] mEquAssociationH = new int[EffectQueue.MAIN_VARIANTS];
-   private static int[] mAndAssociationH = new int[EffectQueue.MAIN_VARIANTS];
-
    private static final int TEX_COMP_SIZE = 5; // 5 four-bytes entities inside the component
 
    private static class TexComponent
@@ -134,20 +135,22 @@ public abstract class MeshBase
      mTexComponent = new ArrayList<>();
      mEffComponent = new ArrayList<>();
 
-     mEquAssociation= new int[MAX_EFFECT_COMPONENTS];
-     mAndAssociation= new int[MAX_EFFECT_COMPONENTS];
+     mNeedAdjustAssociation = true;
+     mAssociationBlock = computeAssociationBlockSize();
+     mAssociations= new int[mAssociationSize/4];
 
      mJobNode = new DeferredJobs.JobNode[1];
 
      for(int i=0; i<MAX_EFFECT_COMPONENTS; i++)
        {
-       mAndAssociation[i] = DEFAULT_ASSOCIATION;
-       mEquAssociation[i] = i;
+       mAssociations[getAndIndex(i)] = DEFAULT_ASSOCIATION;
+       mAssociations[getEquIndex(i)] = i;
        }
 
      mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_READ);
      mVBO2= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_READ);
      mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
+     mUBO = new InternalBuffer(GLES30.GL_UNIFORM_BUFFER           , GLES30.GL_STATIC_READ);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -159,10 +162,12 @@ public abstract class MeshBase
      mInflate    = original.mInflate;
      mNumVertices= original.mNumVertices;
 
-     mAndAssociation= new int[MAX_EFFECT_COMPONENTS];
-     System.arraycopy(original.mAndAssociation, 0, mAndAssociation, 0, MAX_EFFECT_COMPONENTS);
-     mEquAssociation= new int[MAX_EFFECT_COMPONENTS];
-     System.arraycopy(original.mEquAssociation, 0, mEquAssociation, 0, MAX_EFFECT_COMPONENTS);
+     mNeedAdjustAssociation = original.mNeedAdjustAssociation;
+     mAssociationBlock = original.mAssociationBlock;
+
+     int size = original.mAssociations.length;
+     mAssociations= new int[size];
+     System.arraycopy(original.mAssociations, 0, mAssociations, 0, size);
 
      if( deep )
        {
@@ -179,7 +184,71 @@ public abstract class MeshBase
        }
 
      mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
-     mTFO.invalidate();
+     mUBO = new InternalBuffer(GLES30.GL_UNIFORM_BUFFER           , GLES30.GL_STATIC_READ);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * @y.exclude
+ */
+   public static void setAssociationSize(int size)
+     {
+     mAssociationSize = size;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int getAndIndex(int component)
+     {
+     return mAssociationBlock*component;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int getEquIndex(int component)
+     {
+     return mAssociationBlock*(MAX_EFFECT_COMPONENTS+component);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private int computeAssociationBlockSize()
+     {
+     return 1 + (mAssociationSize/4 - 2*MAX_EFFECT_COMPONENTS) / (2*MAX_EFFECT_COMPONENTS-1);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// The problem here is that at MeshBase object creation time, we might not know the size of the
+// 'meshAssociation' Uniform Block Object in the vertex shader (the UBO is packed according to the
+// 'shared' layout, which means the size of the block is known only after linking the shaders).
+//
+// We thus, in the constructor, guess that the layout will be tight (so we will need 8*MAX_EFFECT_C
+// bytes there), allocate mAssociation already, and if later on, as a result of linking the shaders,
+// we get a call to setAssociationSize() which changes the size to something else, we need to reallocate.
+//
+// It can happen that even before the linking the value of mAssociationSize is already 'right' because
+// this is a static variable and it might persist from an earlier run. All the better then; this should
+// never be wrong.
+
+   private void adjustAssociation()
+     {
+     int oldLen = mAssociations.length;
+
+     if( mAssociationSize != 4*oldLen )
+       {
+       int[] tmp = new int[oldLen];
+       System.arraycopy(mAssociations, 0, tmp, 0, oldLen);
+
+       int newLen = mAssociationSize/4;
+       mAssociations = new int[newLen];
+       mAssociationBlock = computeAssociationBlockSize();
+
+       for(int i=0; i<oldLen/2; i++)
+         {
+         mAssociations[getAndIndex(i)] = tmp[i];                         // oldLen must be equal to
+         mAssociations[getEquIndex(i)] = tmp[MAX_EFFECT_COMPONENTS+i];   // 8*MAX_EFFECT_COM
+         }
+       }
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -270,7 +339,7 @@ public abstract class MeshBase
        start = end+1;
        end   = mEffComponent.get(i);
 
-       if( (andAssoc & mAndAssociation[i]) != 0 || (equAssoc == mEquAssociation[i]) )
+       if( (andAssoc & mAssociations[getAndIndex(i)]) != 0 || (equAssoc == mAssociations[getEquIndex(i)]) )
          {
          applyMatrixToComponent(matrixP, matrixV, start, end);
          }
@@ -343,8 +412,10 @@ public abstract class MeshBase
 
    void setEffectAssociationNow(int component, int andAssociation, int equAssociation)
      {
-     mAndAssociation[component] = andAssociation;
-     mEquAssociation[component] = equAssociation;
+     mAssociations[getAndIndex(component)] = andAssociation;
+     mAssociations[getEquIndex(component)] = equAssociation;
+
+     mUBO.invalidate();
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -410,8 +481,8 @@ public abstract class MeshBase
 
          if( origEffComponents<MAX_EFFECT_COMPONENTS )
            {
-           mAndAssociation[origEffComponents] = mesh.mAndAssociation[j];
-           mEquAssociation[origEffComponents] = mesh.mEquAssociation[j];
+           mAssociations[getAndIndex(origEffComponents)] = mesh.mAssociations[mesh.getAndIndex(j)];
+           mAssociations[getEquIndex(origEffComponents)] = mesh.mAssociations[mesh.getEquIndex(j)];
            origEffComponents++;
            }
          }
@@ -561,14 +632,6 @@ public abstract class MeshBase
      return MAX_EFFECT_COMPONENTS;
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public static void getUniforms(int mProgramH, int variant)
-     {
-     mEquAssociationH[variant] = GLES30.glGetUniformLocation( mProgramH, "vComEquAssoc");
-     mAndAssociationH[variant] = GLES30.glGetUniformLocation( mProgramH, "vComAndAssoc");
-     }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Not part of public API, do not document (public only because has to be used from the main package)
@@ -583,7 +646,7 @@ public abstract class MeshBase
        {
        FloatBuffer feedback = buffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
        feedback.get(mVertAttribs1,0,VERT1_ATTRIBS*mNumVertices);
-       mVBO1.update(mVertAttribs1);
+       mVBO1.updateFloat(mVertAttribs1);
        }
      else
        {
@@ -694,7 +757,7 @@ public abstract class MeshBase
  */
    public int getTFO()
      {
-     return mTFO.createImmediately(mNumVertices*TRAN_SIZE, null);
+     return mTFO.createImmediatelyFloat(mNumVertices*TRAN_SIZE, null);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -714,10 +777,18 @@ public abstract class MeshBase
  *
  * @y.exclude
  */
-   public void send(int variant)
+   public void send(int programH)
      {
-     GLES30.glUniform1iv( mEquAssociationH[variant], MAX_EFFECT_COMPONENTS, mEquAssociation, 0);
-     GLES30.glUniform1iv( mAndAssociationH[variant], MAX_EFFECT_COMPONENTS, mAndAssociation, 0);
+     if( mNeedAdjustAssociation )
+       {
+       mNeedAdjustAssociation = false;
+       adjustAssociation();
+       }
+
+     int index = mUBO.createImmediatelyInt( mAssociationSize, mAssociations);
+
+     GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, UBO_BINDING, index);
+     GLES30.glUniformBlockBinding(programH, UBO_BINDING, index);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -746,8 +817,8 @@ public abstract class MeshBase
  */
        }
 
-     int index1 = mVBO1.createImmediately(mNumVertices*VERT1_SIZE, mVertAttribs1);
-     int index2 = mVBO2.createImmediately(mNumVertices*VERT2_SIZE, mVertAttribs2);
+     int index1 = mVBO1.createImmediatelyFloat(mNumVertices*VERT1_SIZE, mVertAttribs1);
+     int index2 = mVBO2.createImmediatelyFloat(mNumVertices*VERT2_SIZE, mVertAttribs2);
 
      GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
      GLES30.glVertexAttribPointer(program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
@@ -767,7 +838,7 @@ public abstract class MeshBase
  */
    public void bindTransformAttribs(DistortedProgram program)
      {
-     int index = mTFO.createImmediately(mNumVertices*TRAN_SIZE, null);
+     int index = mTFO.createImmediatelyFloat(mNumVertices*TRAN_SIZE, null);
 
      GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index );
      GLES30.glVertexAttribPointer(program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, 0);
diff --git a/src/main/res/raw/main_vertex_shader.glsl b/src/main/res/raw/main_vertex_shader.glsl
index 223e125..86fe3b8 100644
--- a/src/main/res/raw/main_vertex_shader.glsl
+++ b/src/main/res/raw/main_vertex_shader.glsl
@@ -39,7 +39,7 @@ out vec3 v_Inflate;                  // Transform Feedback for preapply effects
 out vec3 v_Normal;                   //
 out vec2 v_TexCoordinate;            //
 
-uniform mat4 u_MVPMatrix;            // the combined model/view/projection matrix.
+uniform mat4 u_MVPMatrix;            // u_MVMatrixP * projection.
 uniform mat4 u_MVMatrixP;            // the combined model/view matrix. (for points)
 uniform mat4 u_MVMatrixV;            // the combined model/view matrix. (for vectors)
                                      // which need to work differently on points and vectors
@@ -54,8 +54,12 @@ uniform vec4 vUniforms[3*NUM_VERTEX];// i-th effect is 3 consecutive vec4's: [3*
                                      // second vec4: first float - cache, next 3: Center, the third -  the Region.
 uniform int vEffAndAssoc[NUM_VERTEX];// Associations of the vertex effects. Those are used to connect an effect to a Mesh component.
 uniform int vEffEquAssoc[NUM_VERTEX];// Components the vertex effects work on. Likewise used to connect an effect to a Mesh component.
-uniform int vComAndAssoc[MAX_COMPON];// 'logical AND' association of the component.
-uniform int vComEquAssoc[MAX_COMPON];// 'equal' association of the component.
+
+layout (std140, binding=1) uniform meshAssociation
+  {
+  int vComAndAssoc[MAX_COMPON]; // 'logical AND' association of the component.
+  int vComEquAssoc[MAX_COMPON]; // 'equal' association of the component.
+  };
 
 //////////////////////////////////////////////////////////////////////////////////////////////
 // HELPER FUNCTIONS
