content: /*
   * Copyright 2014 Google Inc. All rights reserved.
   *
   * 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 ai.guiji.duix.sdk.client.util;
  
  import android.app.ActivityManager;
  import android.content.Context;
  import android.content.pm.ConfigurationInfo;
  import android.graphics.Bitmap;
  import android.opengl.GLES10;
  import android.opengl.GLES20;
  import android.opengl.GLES30;
  import android.opengl.GLUtils;
  import android.opengl.Matrix;
  import android.os.Build;
  import android.util.Log;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.FloatBuffer;
  import java.util.Arrays;
  
  import javax.microedition.khronos.egl.EGL10;
  import javax.microedition.khronos.egl.EGLConfig;
  import javax.microedition.khronos.egl.EGLContext;
  import javax.microedition.khronos.egl.EGLDisplay;
  import javax.microedition.khronos.egl.EGLSurface;
  
  public class OpenGLUtil {
      public static final String TAG = "OpenGLUtil";
      public static final int NO_TEXTURE = -1;
      public static final float[] IDENTITY_MATRIX;
      private static final int SIZEOF_FLOAT = 4;
  
      static {
          IDENTITY_MATRIX = new float[16];
          Matrix.setIdentityM(IDENTITY_MATRIX, 0);
      }
  
      public static int loadTexture(final Bitmap img, final int usedTexId) {
          return loadTexture(img, usedTexId, true);
      }
  
      public static int loadTexture(final Bitmap img, final int usedTexId, final boolean recycle) {
          int textures[] = new int[1];
          if (usedTexId == NO_TEXTURE) {
              GLES20.glGenTextures(1, textures, 0);
              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  
              GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, img, 0);
          } else {
              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, usedTexId);
              GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, img);
              textures[0] = usedTexId;
          }
          if (recycle) {
              img.recycle();
          }
          return textures[0];
      }
  
      public static int loadTexture(final ByteBuffer data, final int width, final int height, final int usedTexId) {
          int textures[] = new int[1];
          if (usedTexId == NO_TEXTURE) {
              GLES20.glGenTextures(1, textures, 0);
              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                      GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                      GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                      GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
              GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                      GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
              GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
                      0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data);
          } else {
              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, usedTexId);
              GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
                      height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data);
              textures[0] = usedTexId;
          }
          return textures[0];
      }
  
      public static int createProgram(String vertexSource, String fragmentSource) {
          int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
          if (vertexShader == 0) {
              return 0;
          }
          int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
          if (pixelShader == 0) {
              return 0;
          }
  
          int program = GLES20.glCreateProgram();
          checkGLError("glCreateProgram");
          if (program == 0) {
              Log.e(TAG, "Could not create program");
          }
          GLES20.glAttachShader(program, vertexShader);
          checkGLError("glAttachShader");
          GLES20.glAttachShader(program, pixelShader);
          checkGLError("glAttachShader");
          GLES20.glLinkProgram(program);
          int[] linkStatus = new int[1];
          GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
          if (linkStatus[0] != GLES20.GL_TRUE) {
              Log.e(TAG, "Could not link program: ");
              Log.e(TAG, GLES20.glGetProgramInfoLog(program));
              GLES20.glDeleteProgram(program);
              program = 0;
          }
          return program;
      }
  
      public static int loadShader(int shaderType, String source) {
          int shader = GLES20.glCreateShader(shaderType);
          checkGLError("glCreateShader type=" + shaderType);
          GLES20.glShaderSource(shader, source);
          GLES20.glCompileShader(shader);
          int[] compiled = new int[1];
          GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
          if (compiled[0] == 0) {
              Log.e(TAG, "Could not compile shader " + shaderType + ":");
              Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
              GLES20.glDeleteShader(shader);
              shader = 0;
          }
          return shader;
      }
  
      public static void checkGLError(String op) {
          int error = GLES20.glGetError();
          if (error != GLES20.GL_NO_ERROR) {
              String msg = op + ": glError 0x" + Integer.toHexString(error);
              Log.e(TAG, msg);
          }
      }
  
      public static void checkLocation(int location, String label) {
          if (location < 0) {
              Log.e(TAG, "Unable to locate '" + label + "' in program");
          }
      }
  
      /**
       * Creates a texture from raw data.
       *
       * @param data   Image data, in a "direct" ByteBuffer.
       * @param width  Texture width, in pixels (not bytes).
       * @param height Texture height, in pixels.
       * @param format Image data format (use constant appropriate for glTexImage2D(), e.g. GL_RGBA).
       * @return Handle to texture.
       */
      public static int createImageTexture(ByteBuffer data, int width, int height, int format) {
          int[] textureHandles = new int[1];
          int textureHandle;
  
          GLES20.glGenTextures(1, textureHandles, 0);
          textureHandle = textureHandles[0];
          OpenGLUtil.checkGLError("glGenTextures");
  
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
  
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
                  GLES20.GL_LINEAR);
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
                  GLES20.GL_LINEAR);
          OpenGLUtil.checkGLError("loadImageTexture");
  
          // Load the data from the buffer into the texture handle.
          GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, /*level*/ 0, format,
                  width, height, /*border*/ 0, format, GLES20.GL_UNSIGNED_BYTE, data);
          OpenGLUtil.checkGLError("loadImageTexture");
  
          return textureHandle;
      }
  
      public static int createImageTexture(Bitmap bmp) {
          int[] textureHandles = new int[1];
          int textureHandle;
  
          GLES20.glGenTextures(1, textureHandles, 0);
          textureHandle = textureHandles[0];
          OpenGLUtil.checkGLError("glGenTextures");
  
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle);
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
                  GLES20.GL_LINEAR);
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
                  GLES20.GL_LINEAR);
          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);
          OpenGLUtil.checkGLError("loadImageTexture");
          // Load the data from the buffer into the texture handle.
          GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /*level*/ 0, bmp, 0);
          OpenGLUtil.checkGLError("loadImageTexture");
  
          return textureHandle;
      }
  
      /**
       * Allocates a direct float buffer, and populates it with the float array data.
       */
      public static FloatBuffer createFloatBuffer(float[] coords) {
          // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
          ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT);
          bb.order(ByteOrder.nativeOrder());
          FloatBuffer fb = bb.asFloatBuffer();
          fb.put(coords);
          fb.position(0);
          return fb;
      }
  
      /**
       * Writes GL version info to the log.
       */
      public static void printVersionInfo() {
          Log.i(TAG, "vendor  : " + GLES20.glGetString(GLES20.GL_VENDOR));
          Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER));
          Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION));
  
          int[] values = new int[1];
          GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0);
          int majorVersion = values[0];
          GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0);
          int minorVersion = values[0];
          if (GLES30.glGetError() == GLES30.GL_NO_ERROR) {
              Log.i(TAG, "OpenGL Version: " + majorVersion + "." + minorVersion);
          }
      }
  
      /**
       * 获取OpenGL主版本号，必须在GL线程调用
       *
       * @return 主版本号
       */
      public static int getGlMajorVersion() {
          int[] values = new int[1];
          GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0);
          int majorVersion = values[0];
          return majorVersion;
      }
  
      public static int createTextureObject(int textureTarget) {
          int[] textures = new int[1];
          GLES20.glGenTextures(1, textures, 0);
          OpenGLUtil.checkGLError("glGenTextures");
  
          int texId = textures[0];
          GLES20.glBindTexture(textureTarget, texId);
          OpenGLUtil.checkGLError("glBindTexture " + texId);
  
          GLES20.glTexParameterf(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
          GLES20.glTexParameterf(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
          GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
          GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
          OpenGLUtil.checkGLError("glTexParameter");
  
          return texId;
      }
  
      public static void deleteTextures(int[] textureId) {
          if (textureId != null && textureId.length > 0) {
              GLES20.glDeleteTextures(textureId.length, textureId, 0);
          }
      }
  
      public static void createFrameBuffers(int[] fboTex, int[] fboId, int width, int height) {
          GLES20.glGenFramebuffers(fboId.length, fboId, 0);
          GLES20.glGenTextures(fboTex.length, fboTex, 0);
          GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId[0]);
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTex[0]);
          GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
          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.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
          GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, fboTex[0], 0);
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
          GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
      }
  
      public static void deleteFrameBuffers(int[] fboId) {
          if (fboId != null && fboId.length > 0) {
              GLES20.glDeleteFramebuffers(fboId.length, fboId, 0);
          }
      }
  
      public static float[] changeMvpMatrixCrop(float viewWidth, float viewHeight, float textureWidth, float textureHeight) {
          float scale = viewWidth * textureHeight / viewHeight / textureWidth;
          float[] mvp = Arrays.copyOf(IDENTITY_MATRIX, IDENTITY_MATRIX.length);
          Matrix.scaleM(mvp, 0, scale > 1 ? 1F : (1F / scale), scale > 1 ? scale : 1F, 1F);
  //        Matrix.translateM(mvp,0,0f,0f,-2.5f);
  //        Matrix.rotateM(mvp,0,-60f,1f,0f,0f);
          return mvp;
      }
  
      public static float[] changeMvpMatrixInside(float viewWidth, float viewHeight, float textureWidth, float textureHeight) {
          float scale = viewWidth * textureHeight / viewHeight / textureWidth;
          float[] mvp = Arrays.copyOf(IDENTITY_MATRIX, IDENTITY_MATRIX.length);
          Matrix.scaleM(mvp, 0, scale > 1 ? (1F / scale) : 1F, scale > 1 ? 1F : scale, 1F);
          return mvp;
      }
  
      /**
       * 获取当前设备支持openGL ES版本，优先返回3。0
       *
       * @param context
       * @return ver
       */
      public static int getSupportGLVersion(Context context) {
          final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
          final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
          int version = configurationInfo.reqGlEsVersion >= 0x30000 ? 3 : 2;
          String glEsVersion = configurationInfo.getGlEsVersion();
          Log.d(TAG, "OpenGL ES Version: " + Integer.toHexString(configurationInfo.reqGlEsVersion)
                  + ", glEsVersion: " + glEsVersion + ", return: " + version);
          return version;
      }
  
      /**
       * 获取当前设备支持OpenGL最大纹理大小
       *
       * @return
       */
      public static int getMaxTextureSize() {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
              EGL10 egl = (EGL10) EGLContext.getEGL();
              EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
              int[] vers = new int[2];
              egl.eglInitialize(dpy, vers);
              int[] configAttr = {
                      EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
                      EGL10.EGL_LEVEL, 0,
                      EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
                      EGL10.EGL_NONE
              };
              EGLConfig[] configs = new EGLConfig[1];
              int[] numConfig = new int[1];
              egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
              if (numConfig[0] == 0) {// TROUBLE! No config found.
              }
              EGLConfig config = configs[0];
              int[] surfAttr = {
                      EGL10.EGL_WIDTH, 64,
                      EGL10.EGL_HEIGHT, 64,
                      EGL10.EGL_NONE
              };
              EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
              final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;  // missing in EGL10
              int[] ctxAttrib = {
                      EGL_CONTEXT_CLIENT_VERSION, 1,
                      EGL10.EGL_NONE
              };
              EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
              egl.eglMakeCurrent(dpy, surf, surf, ctx);
              int[] maxSize = new int[1];
              GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
              egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
              egl.eglDestroySurface(dpy, surf);
              egl.eglDestroyContext(dpy, ctx);
              egl.eglTerminate(dpy);
              return maxSize[0];
          } else {
              int[] maxSize = new int[1];
              GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
              return maxSize[0];
          }
      }
  
      /**
       * Helper function that compiles the shaders, links and validates the
       * program, returning the program ID.
       */
      public static int buildProgram(String vertexShaderSource,
                                     String fragmentShaderSource) {
          int program;
  
          // Compile the shaders.
          int vertexShader = compileVertexShader(vertexShaderSource);
          int fragmentShader = compileFragmentShader(fragmentShaderSource);
  
          // Link them into a shader program.
          program = linkProgram(vertexShader, fragmentShader);
  
          validateProgram(program);
  
          return program;
      }
  
      /**
       * Loads and compiles a vertex shader, returning the OpenGL object ID.
       */
      public static int compileVertexShader(String shaderCode) {
          return compileShader(GLES20.GL_VERTEX_SHADER, shaderCode);
      }
  
      /**
       * Loads and compiles a fragment shader, returning the OpenGL object ID.
       */
      public static int compileFragmentShader(String shaderCode) {
          return compileShader(GLES20.GL_FRAGMENT_SHADER, shaderCode);
      }
  
      /**
       * Links a vertex shader and a fragment shader together into an OpenGL
       * program. Returns the OpenGL program object ID, or 0 if linking failed.
       */
      public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
  
          // Create a new program object.
          final int programObjectId = GLES20.glCreateProgram();
  
          if (programObjectId == 0) {
              Log.w(TAG, "Could not create new program");
              return 0;
          }
  
          // Attach the vertex shader to the program.
          GLES20.glAttachShader(programObjectId, vertexShaderId);
  
          // Attach the fragment shader to the program.
          GLES20.glAttachShader(programObjectId, fragmentShaderId);
  
          // Link the two shaders together into a program.
          GLES20.glLinkProgram(programObjectId);
  
          // Get the link status.
          final int[] linkStatus = new int[1];
          GLES20.glGetProgramiv(programObjectId, GLES20.GL_LINK_STATUS,
                  linkStatus, 0);
  
          // Print the program info log to the Android log output.
          Log.v(TAG,
                  "Results of linking program:\n"
                          + GLES20.glGetProgramInfoLog(programObjectId));
  
          // Verify the link status.
          if (linkStatus[0] == 0) {
              // If it failed, delete the program object.
              GLES20.glDeleteProgram(programObjectId);
              Log.w(TAG, "Linking of program failed.");
              return 0;
          }
  
          // Return the program object ID.
          return programObjectId;
      }
  
      /**
       * Validates an OpenGL program. Should only be called when developing the
       * application.
       */
      public static boolean validateProgram(int programObjectId) {
          GLES20.glValidateProgram(programObjectId);
          final int[] validateStatus = new int[1];
          GLES20.glGetProgramiv(programObjectId, GLES20.GL_VALIDATE_STATUS,
                  validateStatus, 0);
          Log.v(TAG, "Results of validating program: " + validateStatus[0]
                  + "\nLog:" + GLES20.glGetProgramInfoLog(programObjectId));
  
          return validateStatus[0] != 0;
      }
  
      /**
       * Compiles a shader, returning the OpenGL object ID.
       */
      private static int compileShader(int type, String shaderCode) {
          // Create a new shader object.
          final int shaderObjectId = GLES20.glCreateShader(type);
  
          if (shaderObjectId == 0) {
              Log.e(TAG, "Could not create new shader.");
              return 0;
          }
  
          // Pass in the shader source.
          GLES20.glShaderSource(shaderObjectId, shaderCode);
  
          // Compile the shader.
          GLES20.glCompileShader(shaderObjectId);
  
          // Get the compilation status.
          final int[] compileStatus = new int[1];
          GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS,
                  compileStatus, 0);
  
          // Verify the compile status.
          if (compileStatus[0] == 0) {
              // If it failed, delete the shader object.
              GLES20.glDeleteShader(shaderObjectId);
              Log.e(TAG, "Compilation of shader failed.");
              return 0;
          }
  
          // Return the shader object ID.
          return shaderObjectId;
      }
  
      public static float[] scale(float[] m) {
          Matrix.scaleM(m, 0, 2.0F, 1.0F, 1);
          return m;
      }
  
      public static float[] rotate(float[] m, float angle) {
          Matrix.rotateM(m, 0, angle, 0, 0, 1);
          return m;
      }
  
      public static float[] flip(float[] m, boolean x, boolean y) {
          if (x || y) {
              Matrix.scaleM(m, 0, x ? -1 : 1, y ? -1 : 1, 1);
          }
          return m;
      }
  
  }
  