content: /*
   * Copyright (C) 2012 CyberAgent
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.seu.magicfilter.filter.base.gpuimage;
  
  import android.graphics.PointF;
  import android.opengl.GLES20;
  
  import com.seu.magicfilter.utils.OpenGlUtils;
  import com.seu.magicfilter.utils.Rotation;
  import com.seu.magicfilter.utils.TextureRotationUtil;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.FloatBuffer;
  import java.util.LinkedList;
  
  public class GPUImageFilter {
      public static final String NO_FILTER_VERTEX_SHADER = "" +
              "attribute vec4 position;\n" +
              "attribute vec4 inputTextureCoordinate;\n" +
              " \n" +
              "varying vec2 textureCoordinate;\n" +
              " \n" +
              "void main()\n" +
              "{\n" +
              "    gl_Position = position;\n" +
              "    textureCoordinate = inputTextureCoordinate.xy;\n" +
              "}";
      public static final String NO_FILTER_FRAGMENT_SHADER = "" +
              "varying highp vec2 textureCoordinate;\n" +
              " \n" +
              "uniform sampler2D inputImageTexture;\n" +
              " \n" +
              "void main()\n" +
              "{\n" +
              "     gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" +
              "}";
  
      private final LinkedList<Runnable> mRunOnDraw;
      private final String mVertexShader;
      private final String mFragmentShader;
      protected int mGLProgId;
      protected int mGLAttribPosition;
      protected int mGLUniformTexture;
      protected int mGLAttribTextureCoordinate;
  
      protected int mIntputWidth;
      protected int mIntputHeight;
      protected boolean mIsInitialized;
      protected FloatBuffer mGLCubeBuffer;
      protected FloatBuffer mGLTextureBuffer;
      protected int mOutputWidth, mOutputHeight;
      
      public GPUImageFilter() {
          this(NO_FILTER_VERTEX_SHADER, NO_FILTER_FRAGMENT_SHADER);
      }
  
      public GPUImageFilter(final String vertexShader, final String fragmentShader) {
          mRunOnDraw = new LinkedList<>();
          mVertexShader = vertexShader;
          mFragmentShader = fragmentShader;
          
          mGLCubeBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.CUBE.length * 4)
                  .order(ByteOrder.nativeOrder())
                  .asFloatBuffer();
          mGLCubeBuffer.put(TextureRotationUtil.CUBE).position(0);
  
          mGLTextureBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
                  .order(ByteOrder.nativeOrder())
                  .asFloatBuffer();
          mGLTextureBuffer.put(TextureRotationUtil.getRotation(Rotation.NORMAL, false, true)).position(0);
      }
  
      public void init() {
          onInit();
          mIsInitialized = true;
          onInitialized();
      }
  
      protected void onInit() {
          mGLProgId = OpenGlUtils.loadProgram(mVertexShader, mFragmentShader);
          mGLAttribPosition = GLES20.glGetAttribLocation(mGLProgId, "position");
          mGLUniformTexture = GLES20.glGetUniformLocation(mGLProgId, "inputImageTexture");
          mGLAttribTextureCoordinate = GLES20.glGetAttribLocation(mGLProgId,
                  "inputTextureCoordinate");
          mIsInitialized = true;
      }
  
      protected void onInitialized() {
  
      }
  
      public final void destroy() {
          mIsInitialized = false;
          GLES20.glDeleteProgram(mGLProgId);
          onDestroy();
      }
  
      protected void onDestroy() {
      }
  
      public void onInputSizeChanged(final int width, final int height) {
          mIntputWidth = width;
          mIntputHeight = height;
      }
  
      public int onDrawFrame(final int textureId, final FloatBuffer cubeBuffer,
                         final FloatBuffer textureBuffer) {
          GLES20.glUseProgram(mGLProgId);
          runPendingOnDrawTasks();
          if (!mIsInitialized) {
              return OpenGlUtils.NOT_INIT;
          }
  
          cubeBuffer.position(0);
          GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT, false, 0, cubeBuffer);
          GLES20.glEnableVertexAttribArray(mGLAttribPosition);
          textureBuffer.position(0);
          GLES20.glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0,
                  textureBuffer);
          GLES20.glEnableVertexAttribArray(mGLAttribTextureCoordinate);
          if (textureId != OpenGlUtils.NO_TEXTURE) {
              GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
              GLES20.glUniform1i(mGLUniformTexture, 0);
          }
          onDrawArraysPre();
          GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
          GLES20.glDisableVertexAttribArray(mGLAttribPosition);
          GLES20.glDisableVertexAttribArray(mGLAttribTextureCoordinate);
          onDrawArraysAfter();
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
          return OpenGlUtils.ON_DRAWN;
      }
      
      public int onDrawFrame(final int textureId) {
  		GLES20.glUseProgram(mGLProgId);
  		runPendingOnDrawTasks();
  		if (!mIsInitialized) 
  			return OpenGlUtils.NOT_INIT;
  		
  		mGLCubeBuffer.position(0);
  		GLES20.glVertexAttribPointer(mGLAttribPosition, 2, GLES20.GL_FLOAT, false, 0, mGLCubeBuffer);
  		GLES20.glEnableVertexAttribArray(mGLAttribPosition);
  		mGLTextureBuffer.position(0);
  		GLES20.glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0,
  		     mGLTextureBuffer);
  		GLES20.glEnableVertexAttribArray(mGLAttribTextureCoordinate);
  
  		if (textureId != OpenGlUtils.NO_TEXTURE) {
              GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  		    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
  		    GLES20.glUniform1i(mGLUniformTexture, 0);
  		}
  		onDrawArraysPre();
  		GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
  		GLES20.glDisableVertexAttribArray(mGLAttribPosition);
  		GLES20.glDisableVertexAttribArray(mGLAttribTextureCoordinate);
  		onDrawArraysAfter();
  		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
  		return OpenGlUtils.ON_DRAWN;
  	}
      
      protected void onDrawArraysPre() {}
      protected void onDrawArraysAfter() {}
      
      protected void runPendingOnDrawTasks() {
          while (!mRunOnDraw.isEmpty()) {
              mRunOnDraw.removeFirst().run();
          }
      }
  
      public boolean isInitialized() {
          return mIsInitialized;
      }
  
      public int getIntputWidth() {
          return mIntputWidth;
      }
  
      public int getIntputHeight() {
          return mIntputHeight;
      }
  
      public int getProgram() {
          return mGLProgId;
      }
  
      public int getAttribPosition() {
          return mGLAttribPosition;
      }
  
      public int getAttribTextureCoordinate() {
          return mGLAttribTextureCoordinate;
      }
  
      public int getUniformTexture() {
          return mGLUniformTexture;
      }
  
      protected void setInteger(final int location, final int intValue) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  GLES20.glUniform1i(location, intValue);
              }
          });
      }
  
      protected void setFloat(final int location, final float floatValue) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  GLES20.glUniform1f(location, floatValue);
              }
          });
      }
  
      protected void setFloatVec2(final int location, final float[] arrayValue) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  GLES20.glUniform2fv(location, 1, FloatBuffer.wrap(arrayValue));
              }
          });
      }
  
      protected void setFloatVec3(final int location, final float[] arrayValue) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  GLES20.glUniform3fv(location, 1, FloatBuffer.wrap(arrayValue));
              }
          });
      }
  
      protected void setFloatVec4(final int location, final float[] arrayValue) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  GLES20.glUniform4fv(location, 1, FloatBuffer.wrap(arrayValue));
              }
          });
      }
  
      protected void setFloatArray(final int location, final float[] arrayValue) {
          runOnDraw(new Runnable() {
              @Override
              public void run() {
                  GLES20.glUniform1fv(location, arrayValue.length, FloatBuffer.wrap(arrayValue));
              }
          });
      }
  
      protected void setPoint(final int location, final PointF point) {
          runOnDraw(new Runnable() {
  
              @Override
              public void run() {
                  float[] vec2 = new float[2];
                  vec2[0] = point.x;
                  vec2[1] = point.y;
                  GLES20.glUniform2fv(location, 1, vec2, 0);
              }
          });
      }
  
      protected void setUniformMatrix3f(final int location, final float[] matrix) {
          runOnDraw(new Runnable() {
  
              @Override
              public void run() {
                  GLES20.glUniformMatrix3fv(location, 1, false, matrix, 0);
              }
          });
      }
  
      protected void setUniformMatrix4f(final int location, final float[] matrix) {
          runOnDraw(new Runnable() {
  
              @Override
              public void run() {
                  GLES20.glUniformMatrix4fv(location, 1, false, matrix, 0);
              }
          });
      }
  
      protected void runOnDraw(final Runnable runnable) {
          synchronized (mRunOnDraw) {
              mRunOnDraw.addLast(runnable);
          }
      }
  
      public void onDisplaySizeChanged(final int width, final int height) {
      	mOutputWidth = width;
      	mOutputHeight = height;
      }
  }
  