title: OpenGL ES 2 using GL_LUMINANCE with GL_FLOAT on Android devices
body: <p>Using Android Studio, my code renders an array of floats as a texture passed to GLSL with one float per texel in the range of 0 to 1, like a grayscale texture. For that i use GL_LUMINANCE as the internalFormat and format for glTexImage2D and GL_FLOAT for type. Running the app on an android device emulator works fine (which uses my PC's GPU), but on a real device (Samsung Galaxy S7) calling glTexImage2D gives error 1282, GL_INVALID_OPERATION. I thought it might be a problem with non power of two textures, but the width and height are certainly powers of two.</p>
  
  <p>The code uses <a href="http://www.dgp.toronto.edu/people/stam/reality/Research/pub.html" rel="nofollow noreferrer">Jos Stam fluid simulation C code</a> (compiled with the NDK, not ported) which outputs density values for a grid.</p>
  
  <p>mSizeN is the width (same as height) of the fluid simulation grid, although 2 is added to it by the fluid sim for boundary conditions, so the width of the array returned is mSizeN + 2; 128 in this case.</p>
  
  <p>The coordinate system is set up as an orthographic projection with 0.0,0.0 the top left of the screen, 1.0,1.0 is the bottom right. I just draw a full screen quad and use the interpolated position across the quad in GLSL as texture coordinates to the array containing density values. Nice easy way to render it.</p>
  
  <p>This is the renderer class.</p>
  
  <pre><code>public class GLFluidsimRenderer implements GLWallpaperService.Renderer {
  
      private final String TAG = "GLFluidsimRenderer";
  
      private FluidSolver mFluidSolver = new FluidSolver();
  
      private float[] mProjectionMatrix = new float[16];
  
      private final FloatBuffer mFullScreenQuadVertices;
  
      private Context mActivityContext;
  
      private int mProgramHandle;
      private int mProjectionMatrixHandle;
      private int mDensityArrayHandle;
      private int mPositionHandle;
      private int mGridSizeHandle;
  
      private final int mBytesPerFloat = 4;
      private final int mStrideBytes = 3 * mBytesPerFloat;
      private final int mPositionOffset = 0;
      private final int mPositionDataSize = 3;
  
      private int mDensityTexId;
  
      public static int mSizeN = 126;
  
      public GLFluidsimRenderer(final Context activityContext) {
          mActivityContext = activityContext;
  
          final float[] fullScreenQuadVerticesData = {
                  0.0f, 0.0f, 0.0f,
                  0.0f, 1.0f, 0.0f,
                  1.0f, 0.0f, 0.0f,
                  1.0f, 1.0f, 0.0f,
          };
  
          mFullScreenQuadVertices = ByteBuffer.allocateDirect(fullScreenQuadVerticesData.length * mBytesPerFloat)
                  .order(ByteOrder.nativeOrder()).asFloatBuffer();
          mFullScreenQuadVertices.put(fullScreenQuadVerticesData).position(0);
      }
  
      public void onTouchEvent(MotionEvent event) {
  
      }
  
      @Override
      public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
          GLES20.glDisable(GLES20.GL_DEPTH_TEST);
          GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
  
          String vertShader = AssetReader.getStringAsset(mActivityContext, "fluidVertShader");
          String fragShader = AssetReader.getStringAsset(mActivityContext, "fluidFragDensityShader");
  
          final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertShader);
          final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragShader);
  
          mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
                  new String[] {"a_Position"});
  
          mDensityTexId = TextureHelper.loadTextureLumF(mActivityContext, null, mSizeN + 2, mSizeN + 2);
      }
  
  
      @Override
      public void onSurfaceChanged(GL10 glUnused, int width, int height) {
          mFluidSolver.init(width, height, mSizeN);
  
          GLES20.glViewport(0, 0, width, height);
          Matrix.setIdentityM(mProjectionMatrix, 0);
          Matrix.orthoM(mProjectionMatrix, 0, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f);
      }
  
      @Override
      public void onDrawFrame(GL10 glUnused) {
          GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
  
          GLES20.glUseProgram(mProgramHandle);
  
          mFluidSolver.step();
  
          TextureHelper.updateTextureLumF(mFluidSolver.get_density(), mDensityTexId, mSizeN + 2, mSizeN + 2);
  
          mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_ProjectionMatrix");
          mDensityArrayHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_aDensity");
          mGridSizeHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_GridSize");
          mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
  
          double start = System.nanoTime();
          drawQuad(mFullScreenQuadVertices);
          double end = System.nanoTime();
      }
  
      private void drawQuad(final FloatBuffer aQuadBuffer) {
          // Pass in the position information
          aQuadBuffer.position(mPositionOffset);
          GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
                  mStrideBytes, aQuadBuffer);
          GLES20.glEnableVertexAttribArray(mPositionHandle);
  
          // Attach density array to texture unit 0
          GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mDensityTexId);
          GLES20.glUniform1i(mDensityArrayHandle, 0);
  
          // Pass in the actual size of the grid.
          GLES20.glUniform1i(mGridSizeHandle, mSizeN + 2);
  
          GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
          GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
      }
  }
  </code></pre>
  
  <p>Here's the texture helper functions.</p>
  
  <pre><code>public static int loadTextureLumF(final Context context, final float[] data, final int width, final int height) {
      final int[] textureHandle = new int[1];
  
      GLES20.glGenTextures(1, textureHandle, 0);
  
      if (textureHandle[0] != 0) {
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
  
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
  
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  
          GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
          GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1);
  
          GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
                  (int) width, (int) height, 0, GLES20.GL_LUMINANCE, GLES20.GL_FLOAT,
                  (data != null ? FloatBuffer.wrap(data) : null));
      }
  
      if (textureHandle[0] == 0)
          throw new RuntimeException("Error loading texture.");
  
      return textureHandle[0];
  }
  
  public static void updateTextureLumF(final float[] data, final int texId, final int w, final int h) {
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
      GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, (int)w, (int)h, GLES20.GL_LUMINANCE, GLES20.GL_FLOAT, (data != null ? FloatBuffer.wrap(data) : null));
  }
  </code></pre>
  
  <p>Fragment shader.</p>
  
  <pre><code>precision mediump float;
  
  uniform sampler2D u_aDensity;
  uniform int u_GridSize;
  
  varying vec4 v_Color;
  varying vec4 v_Position;
  
  void main()
  {
      gl_FragColor = texture2D(u_aDensity, vec2(v_Position.x, v_Position.y));
  }
  </code></pre>
  
  <p>Is the combination of GL_FLOAT and GL_LUMINANCE unsupported in OpenGL ES 2?</p>
  
  <p><a href="https://i.stack.imgur.com/gDeB8.jpg" rel="nofollow noreferrer">android emulator</a> pic.</p>
  
  <p>edit:
  To add, am i right in saying that each floating point value will be reduced to an 8-bit integer component when transferred with glTexImage2D (etc), so the majority of the floating point precision will be lost? In that case, it might be best to rethink the implementation of the simulator to output fixed point. That can be done easily, Stam even describes it in his paper.</p>