Android: Add support for OpenGL ES 3

Bug: webrtc:10642
Change-Id: I736e9e2520b364a817228a6599f4008d58165622
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/137424
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Commit-Queue: Magnus Jedvert <magjed@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27990}
diff --git a/sdk/android/api/org/webrtc/EglBase.java b/sdk/android/api/org/webrtc/EglBase.java
index 3d0238d..c1cb906 100644
--- a/sdk/android/api/org/webrtc/EglBase.java
+++ b/sdk/android/api/org/webrtc/EglBase.java
@@ -13,6 +13,7 @@
 import android.graphics.SurfaceTexture;
 import android.support.annotation.Nullable;
 import android.view.Surface;
+import java.util.ArrayList;
 import javax.microedition.khronos.egl.EGL10;
 
 /**
@@ -44,51 +45,105 @@
   // This is similar to how GlSurfaceView does:
   // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/opengl/GLSurfaceView.java#760
   public static final int EGL_OPENGL_ES2_BIT = 4;
+  public static final int EGL_OPENGL_ES3_BIT = 0x40;
   // Android-specific extension.
   public static final int EGL_RECORDABLE_ANDROID = 0x3142;
 
-  // clang-format off
-  public static final int[] CONFIG_PLAIN = {
-    EGL10.EGL_RED_SIZE, 8,
-    EGL10.EGL_GREEN_SIZE, 8,
-    EGL10.EGL_BLUE_SIZE, 8,
-    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-    EGL10.EGL_NONE
-  };
-  public static final int[] CONFIG_RGBA = {
-    EGL10.EGL_RED_SIZE, 8,
-    EGL10.EGL_GREEN_SIZE, 8,
-    EGL10.EGL_BLUE_SIZE, 8,
-    EGL10.EGL_ALPHA_SIZE, 8,
-    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-    EGL10.EGL_NONE
-  };
-  public static final int[] CONFIG_PIXEL_BUFFER = {
-    EGL10.EGL_RED_SIZE, 8,
-    EGL10.EGL_GREEN_SIZE, 8,
-    EGL10.EGL_BLUE_SIZE, 8,
-    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
-    EGL10.EGL_NONE
-  };
-  public static final int[] CONFIG_PIXEL_RGBA_BUFFER = {
-    EGL10.EGL_RED_SIZE, 8,
-    EGL10.EGL_GREEN_SIZE, 8,
-    EGL10.EGL_BLUE_SIZE, 8,
-    EGL10.EGL_ALPHA_SIZE, 8,
-    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
-    EGL10.EGL_NONE
-  };
-  public static final int[] CONFIG_RECORDABLE = {
-    EGL10.EGL_RED_SIZE, 8,
-    EGL10.EGL_GREEN_SIZE, 8,
-    EGL10.EGL_BLUE_SIZE, 8,
-    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-    EGL_RECORDABLE_ANDROID, 1,
-    EGL10.EGL_NONE
-  };
-  // clang-format on
+  public static ConfigBuilder configBuilder() {
+    return new ConfigBuilder();
+  }
+
+  public static class ConfigBuilder {
+    private int openGlesVersion = 2;
+    private boolean hasAlphaChannel;
+    private boolean supportsPixelBuffer;
+    private boolean isRecordable;
+
+    public ConfigBuilder setOpenGlesVersion(int version) {
+      if (version < 1 || version > 3) {
+        throw new IllegalArgumentException("OpenGL ES version " + version + " not supported");
+      }
+      this.openGlesVersion = version;
+      return this;
+    }
+
+    public ConfigBuilder setHasAlphaChannel(boolean hasAlphaChannel) {
+      this.hasAlphaChannel = hasAlphaChannel;
+      return this;
+    }
+
+    public ConfigBuilder setSupportsPixelBuffer(boolean supportsPixelBuffer) {
+      this.supportsPixelBuffer = supportsPixelBuffer;
+      return this;
+    }
+
+    public ConfigBuilder setIsRecordable(boolean isRecordable) {
+      this.isRecordable = isRecordable;
+      return this;
+    }
+
+    public int[] createConfigAttributes() {
+      ArrayList<Integer> list = new ArrayList<>();
+      list.add(EGL10.EGL_RED_SIZE);
+      list.add(8);
+      list.add(EGL10.EGL_GREEN_SIZE);
+      list.add(8);
+      list.add(EGL10.EGL_BLUE_SIZE);
+      list.add(8);
+      if (hasAlphaChannel) {
+        list.add(EGL10.EGL_ALPHA_SIZE);
+        list.add(8);
+      }
+      if (openGlesVersion == 2 || openGlesVersion == 3) {
+        list.add(EGL10.EGL_RENDERABLE_TYPE);
+        list.add(openGlesVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT);
+      }
+      if (supportsPixelBuffer) {
+        list.add(EGL10.EGL_SURFACE_TYPE);
+        list.add(EGL10.EGL_PBUFFER_BIT);
+      }
+      if (isRecordable) {
+        list.add(EGL_RECORDABLE_ANDROID);
+        list.add(1);
+      }
+      list.add(EGL10.EGL_NONE);
+
+      final int[] res = new int[list.size()];
+      for (int i = 0; i < list.size(); ++i) {
+        res[i] = list.get(i);
+      }
+      return res;
+    }
+  }
+
+  public static final int[] CONFIG_PLAIN = configBuilder().createConfigAttributes();
+  public static final int[] CONFIG_RGBA =
+      configBuilder().setHasAlphaChannel(true).createConfigAttributes();
+  public static final int[] CONFIG_PIXEL_BUFFER =
+      configBuilder().setSupportsPixelBuffer(true).createConfigAttributes();
+  public static final int[] CONFIG_PIXEL_RGBA_BUFFER = configBuilder()
+                                                           .setHasAlphaChannel(true)
+                                                           .setSupportsPixelBuffer(true)
+                                                           .createConfigAttributes();
+  public static final int[] CONFIG_RECORDABLE =
+      configBuilder().setIsRecordable(true).createConfigAttributes();
+
+  static int getOpenGlesVersionFromConfig(int[] configAttributes) {
+    for (int i = 0; i < configAttributes.length - 1; ++i) {
+      if (configAttributes[i] == EGL10.EGL_RENDERABLE_TYPE) {
+        switch (configAttributes[i + 1]) {
+          case EGL_OPENGL_ES2_BIT:
+            return 2;
+          case EGL_OPENGL_ES3_BIT:
+            return 3;
+          default:
+            return 1;
+        }
+      }
+    }
+    // Default to V1 if no renderable type is specified.
+    return 1;
+  }
 
   /**
    * Create a new context with the specified config attributes, sharing data with |sharedContext|.
diff --git a/sdk/android/src/java/org/webrtc/EglBase10Impl.java b/sdk/android/src/java/org/webrtc/EglBase10Impl.java
index 08f6620..3ae38f0 100644
--- a/sdk/android/src/java/org/webrtc/EglBase10Impl.java
+++ b/sdk/android/src/java/org/webrtc/EglBase10Impl.java
@@ -27,6 +27,7 @@
  * and an EGLSurface.
  */
 class EglBase10Impl implements EglBase10 {
+  private static final String TAG = "EglBase10Impl";
   // This constant is taken from EGL14.EGL_CONTEXT_CLIENT_VERSION.
   private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
 
@@ -64,7 +65,9 @@
     this.egl = (EGL10) EGLContext.getEGL();
     eglDisplay = getEglDisplay();
     eglConfig = getEglConfig(eglDisplay, configAttributes);
-    eglContext = createEglContext(sharedContext, eglDisplay, eglConfig);
+    final int openGlesVersion = EglBase.getOpenGlesVersionFromConfig(configAttributes);
+    Logging.d(TAG, "Using OpenGL ES version " + openGlesVersion);
+    eglContext = createEglContext(sharedContext, eglDisplay, eglConfig, openGlesVersion);
   }
 
   @Override
@@ -309,12 +312,12 @@
   }
 
   // Return an EGLConfig, or die trying.
-  private EGLContext createEglContext(
-      @Nullable EGLContext sharedContext, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+  private EGLContext createEglContext(@Nullable EGLContext sharedContext, EGLDisplay eglDisplay,
+      EGLConfig eglConfig, int openGlesVersion) {
     if (sharedContext != null && sharedContext == EGL10.EGL_NO_CONTEXT) {
       throw new RuntimeException("Invalid sharedContext");
     }
-    int[] contextAttributes = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
+    int[] contextAttributes = {EGL_CONTEXT_CLIENT_VERSION, openGlesVersion, EGL10.EGL_NONE};
     EGLContext rootContext = sharedContext == null ? EGL10.EGL_NO_CONTEXT : sharedContext;
     final EGLContext eglContext;
     synchronized (EglBase.lock) {
diff --git a/sdk/android/src/java/org/webrtc/EglBase14Impl.java b/sdk/android/src/java/org/webrtc/EglBase14Impl.java
index 07fbe46..1c519ff 100644
--- a/sdk/android/src/java/org/webrtc/EglBase14Impl.java
+++ b/sdk/android/src/java/org/webrtc/EglBase14Impl.java
@@ -30,7 +30,7 @@
 @SuppressWarnings("ReferenceEquality") // We want to compare to EGL14 constants.
 @TargetApi(18)
 class EglBase14Impl implements EglBase14 {
-  private static final String TAG = "EglBase14";
+  private static final String TAG = "EglBase14Impl";
   private static final int EGLExt_SDK_VERSION = Build.VERSION_CODES.JELLY_BEAN_MR2;
   private static final int CURRENT_SDK_VERSION = Build.VERSION.SDK_INT;
   private EGLContext eglContext;
@@ -73,7 +73,9 @@
   public EglBase14Impl(EGLContext sharedContext, int[] configAttributes) {
     eglDisplay = getEglDisplay();
     eglConfig = getEglConfig(eglDisplay, configAttributes);
-    eglContext = createEglContext(sharedContext, eglDisplay, eglConfig);
+    final int openGlesVersion = EglBase.getOpenGlesVersionFromConfig(configAttributes);
+    Logging.d(TAG, "Using OpenGL ES version " + openGlesVersion);
+    eglContext = createEglContext(sharedContext, eglDisplay, eglConfig, openGlesVersion);
   }
 
   // Create EGLSurface from the Android Surface.
@@ -262,12 +264,12 @@
   }
 
   // Return an EGLConfig, or die trying.
-  private static EGLContext createEglContext(
-      @Nullable EGLContext sharedContext, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+  private static EGLContext createEglContext(@Nullable EGLContext sharedContext,
+      EGLDisplay eglDisplay, EGLConfig eglConfig, int openGlesVersion) {
     if (sharedContext != null && sharedContext == EGL14.EGL_NO_CONTEXT) {
       throw new RuntimeException("Invalid sharedContext");
     }
-    int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
+    int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, openGlesVersion, EGL14.EGL_NONE};
     EGLContext rootContext = sharedContext == null ? EGL14.EGL_NO_CONTEXT : sharedContext;
     final EGLContext eglContext;
     synchronized (EglBase.lock) {