content: /*
   * Copyright 2013 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 io.agora.api.example.common.gles.core;
  
  import android.graphics.SurfaceTexture;
  import android.opengl.EGL14;
  import android.opengl.EGLConfig;
  import android.opengl.EGLContext;
  import android.opengl.EGLDisplay;
  import android.opengl.EGLExt;
  import android.opengl.EGLSurface;
  import android.util.Log;
  import android.view.Surface;
  
  /**
   * Core EGL state (display, context, config).
   * <p>
   * The EGLContext must only be attached to one thread at a time.  This class is not thread-safe.
   */
  public final class EglCore {
      private static final String TAG = GlUtil.TAG;
  
      /**
       * Constructor flag: surface must be recordable.  This discourages EGL from using a
       * pixel format that cannot be converted efficiently to something usable by the video
       * encoder.
       */
      public static final int FLAG_RECORDABLE = 0x01;
  
      /**
       * Constructor flag: ask for GLES3, fall back to GLES2 if not available.  Without this
       * flag, GLES2 is used.
       */
      public static final int FLAG_TRY_GLES3 = 0x02;
  
      // Android-specific extension.
      private static final int EGL_RECORDABLE_ANDROID = 0x3142;
  
      private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
      private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
      private EGLConfig mEGLConfig = null;
      private int mGlVersion = -1;
  
  
      /**
       * Prepares EGL display and context.
       * <p>
       * Equivalent to EglCore(null, 0).
       */
      public EglCore() {
          this(null, 0);
      }
  
      /**
       * Prepares EGL display and context.
       * <p>
       *
       * @param sharedContext The context to share, or null if sharing is not desired.
       * @param flags         Configuration bit flags, e.g. FLAG_RECORDABLE.
       */
      public EglCore(EGLContext sharedContext, int flags) {
          if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
              throw new RuntimeException("EGL already set up");
          }
  
          if (sharedContext == null) {
              sharedContext = EGL14.EGL_NO_CONTEXT;
          }
  
          mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
          if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
              throw new RuntimeException("unable to get EGL14 display");
          }
          int[] version = new int[2];
          if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
              mEGLDisplay = null;
              throw new RuntimeException("unable to initialize EGL14");
          }
  
          // Try to get a GLES3 context, if requested.
          if ((flags & FLAG_TRY_GLES3) != 0) {
              //Log.d(TAG, "Trying GLES 3");
              EGLConfig config = getConfig(flags, 3);
              if (config != null) {
                  int[] attrib3_list = {
                          EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
                          EGL14.EGL_NONE
                  };
                  EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
                          attrib3_list, 0);
  
                  if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
                      //Log.d(TAG, "Got GLES 3 config");
                      mEGLConfig = config;
                      mEGLContext = context;
                      mGlVersion = 3;
                  }
              }
          }
          if (mEGLContext == EGL14.EGL_NO_CONTEXT) {  // GLES 2 only, or GLES 3 attempt failed
              //Log.d(TAG, "Trying GLES 2");
              EGLConfig config = getConfig(flags, 2);
              if (config == null) {
                  throw new RuntimeException("Unable to find a suitable EGLConfig");
              }
              int[] attrib2_list = {
                      EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                      EGL14.EGL_NONE
              };
              EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
                      attrib2_list, 0);
              checkEglError("eglCreateContext");
              mEGLConfig = config;
              mEGLContext = context;
              mGlVersion = 2;
          }
  
          // Confirm with query.
          int[] values = new int[1];
          EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
                  values, 0);
          Log.d(TAG, "EGLContext created, client version " + values[0]);
      }
  
      /**
       * Finds a suitable EGLConfig.
       *
       * @param flags   Bit flags from constructor.
       * @param version Must be 2 or 3.
       * @return EGLConfig
       */
      private EGLConfig getConfig(int flags, int version) {
          int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
          if (version >= 3) {
              renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
          }
  
          // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
          // doesn't really help.  It can also lead to a huge performance hit on glReadPixels()
          // when reading into a GL_RGBA buffer.
          int[] attribList = {
                  EGL14.EGL_RED_SIZE, 8,
                  EGL14.EGL_GREEN_SIZE, 8,
                  EGL14.EGL_BLUE_SIZE, 8,
                  EGL14.EGL_ALPHA_SIZE, 8,
                  //EGL14.EGL_DEPTH_SIZE, 16,
                  //EGL14.EGL_STENCIL_SIZE, 8,
                  EGL14.EGL_RENDERABLE_TYPE, renderableType,
                  EGL14.EGL_NONE, 0,      // placeholder for recordable [@-3]
                  EGL14.EGL_NONE
          };
          if ((flags & FLAG_RECORDABLE) != 0) {
              attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID;
              attribList[attribList.length - 2] = 1;
          }
          EGLConfig[] configs = new EGLConfig[1];
          int[] numConfigs = new int[1];
          if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
                  numConfigs, 0)) {
              Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
              return null;
          }
          return configs[0];
      }
  
      /**
       * Discards all resources held by this class, notably the EGL context.  This must be
       * called from the thread where the context was created.
       * <p>
       * On completion, no context will be current.
       */
      public void release() {
          if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
              // Android is unusual in that it uses a reference-counted EGLDisplay.  So for
              // every eglInitialize() we need an eglTerminate().
              EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                      EGL14.EGL_NO_CONTEXT);
              EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
              EGL14.eglReleaseThread();
              EGL14.eglTerminate(mEGLDisplay);
          }
  
          mEGLDisplay = EGL14.EGL_NO_DISPLAY;
          mEGLContext = EGL14.EGL_NO_CONTEXT;
          mEGLConfig = null;
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
                  // We're limited here -- finalizers don't run on the thread that holds
                  // the EGL state, so if a surface or context is still current on another
                  // thread we can't fully release it here.  Exceptions thrown from here
                  // are quietly discarded.  Complain in the log file.
                  Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
                  release();
              }
          } finally {
              super.finalize();
          }
      }
  
      /**
       * Gets egl context.
       *
       * @return the egl context
       */
      public EGLContext getEGLContext() {
          return mEGLContext;
      }
  
      /**
       * Destroys the specified surface.  Note the EGLSurface won't actually be destroyed if it's
       * still current in a context.
       *
       * @param eglSurface the egl surface
       */
      public void releaseSurface(EGLSurface eglSurface) {
          EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
      }
  
      /**
       * Creates an EGL surface associated with a Surface.
       * <p>
       * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
       *
       * @param surface the surface
       * @return the egl surface
       */
      public EGLSurface createWindowSurface(Object surface) {
          if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
              throw new RuntimeException("invalid surface: " + surface);
          }
  
          // Create a window surface, and attach it to the Surface we received.
          int[] surfaceAttribs = {
                  EGL14.EGL_NONE
          };
          EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface,
                  surfaceAttribs, 0);
          checkEglError("eglCreateWindowSurface");
          if (eglSurface == null) {
              throw new RuntimeException("surface was null");
          }
          return eglSurface;
      }
  
      /**
       * Creates an EGL surface associated with an offscreen buffer.
       *
       * @param width  the width
       * @param height the height
       * @return the egl surface
       */
      public EGLSurface createOffscreenSurface(int width, int height) {
          int[] surfaceAttribs = {
                  EGL14.EGL_WIDTH, width,
                  EGL14.EGL_HEIGHT, height,
                  EGL14.EGL_NONE
          };
          EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig,
                  surfaceAttribs, 0);
          checkEglError("eglCreatePbufferSurface");
          if (eglSurface == null) {
              throw new RuntimeException("surface was null");
          }
          return eglSurface;
      }
  
      /**
       * Makes our EGL context current, using the supplied surface for both "draw" and "read".
       *
       * @param eglSurface the egl surface
       */
      public void makeCurrent(EGLSurface eglSurface) {
          if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
              // called makeCurrent() before create?
              Log.d(TAG, "NOTE: makeCurrent w/o display");
          }
          if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
              throw new RuntimeException("eglMakeCurrent failed");
          }
      }
  
      /**
       * Makes our EGL context current, using the supplied "draw" and "read" surfaces.
       *
       * @param drawSurface the draw surface
       * @param readSurface the read surface
       */
      public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
          if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
              // called makeCurrent() before create?
              Log.d(TAG, "NOTE: makeCurrent w/o display");
          }
          if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
              throw new RuntimeException("eglMakeCurrent(draw,read) failed");
          }
      }
  
      /**
       * Makes no context current.
       */
      public void makeNothingCurrent() {
          if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                  EGL14.EGL_NO_CONTEXT)) {
              throw new RuntimeException("eglMakeCurrent failed");
          }
      }
  
      /**
       * Calls eglSwapBuffers.  Use this to "publish" the current frame.
       *
       * @param eglSurface the egl surface
       * @return false on failure
       */
      public boolean swapBuffers(EGLSurface eglSurface) {
          return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
      }
  
      /**
       * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
       *
       * @param eglSurface the egl surface
       * @param nsecs      the nsecs
       */
      public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
          EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
      }
  
      /**
       * Returns true if our context and the specified surface are current.
       *
       * @param eglSurface the egl surface
       * @return the boolean
       */
      public boolean isCurrent(EGLSurface eglSurface) {
          return mEGLContext.equals(EGL14.eglGetCurrentContext())
                  && eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
      }
  
      /**
       * Gets current drawing surface.
       *
       * @return the current drawing surface
       */
      public EGLSurface getCurrentDrawingSurface() {
          EGLSurface surface = null;
          if (mEGLContext.equals(EGL14.eglGetCurrentContext())) {
              surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
          }
          return surface;
      }
  
      /**
       * Performs a simple surface query.
       *
       * @param eglSurface the egl surface
       * @param what       the what
       * @return the int
       */
      public int querySurface(EGLSurface eglSurface, int what) {
          int[] value = new int[1];
          EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0);
          return value[0];
      }
  
      /**
       * Queries a string value.
       *
       * @param what the what
       * @return the string
       */
      public String queryString(int what) {
          return EGL14.eglQueryString(mEGLDisplay, what);
      }
  
      /**
       * Returns the GLES version this context is configured for (currently 2 or 3).
       *
       * @return the gl version
       */
      public int getGlVersion() {
          return mGlVersion;
      }
  
      /**
       * Writes the current display, context, and surface to the log.
       *
       * @param msg the msg
       */
      public static void logCurrent(String msg) {
          EGLDisplay display;
          EGLContext context;
          EGLSurface surface;
  
          display = EGL14.eglGetCurrentDisplay();
          context = EGL14.eglGetCurrentContext();
          surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
          Log.i(TAG, "Current EGL ("
                  + msg
                  + "): display="
                  + display
                  + ", context="
                  + context
                  + ", surface="
                  + surface);
      }
  
      /**
       * Checks for EGL errors.  Throws an exception if an error has been raised.
       *
       * @param msg error message.
       */
      private void checkEglError(String msg) {
          int error = EGL14.eglGetError();
          if (error != EGL14.EGL_SUCCESS) {
              throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
          }
      }
  }
  