blob: 427e6bf6f7e56930672f705c1a388cb1ad4270dc [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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.google.android.exoplayer2.util;
import static android.opengl.GLU.gluErrorString;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.microedition.khronos.egl.EGL10;
/** OpenGL ES 2.0 utilities. */
public final class GlUtil {
/** Thrown when an OpenGL error occurs and {@link #glAssertionsEnabled} is {@code true}. */
public static final class GlException extends RuntimeException {
/** Creates an instance with the specified error message. */
public GlException(String message) {
super(message);
}
}
/**
* Represents a GLSL shader program.
*
* <p>After constructing a program, keep a reference for its lifetime and call {@link #delete()}
* (or release the current GL context) when it's no longer needed.
*/
public static final class Program {
/** The identifier of a compiled and linked GLSL shader program. */
private final int programId;
private final Attribute[] attributes;
private final Uniform[] uniforms;
private final Map<String, Attribute> attributeByName;
private final Map<String, Uniform> uniformByName;
/**
* Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code.
*
* @param context The {@link Context}.
* @param vertexShaderFilePath The path to a vertex shader program.
* @param fragmentShaderFilePath The path to a fragment shader program.
* @throws IOException When failing to read shader files.
*/
public Program(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
throws IOException {
this(loadAsset(context, vertexShaderFilePath), loadAsset(context, fragmentShaderFilePath));
}
/**
* Creates a GL shader program from vertex and fragment shader GLSL GLES20 code.
*
* <p>This involves slow steps, like compiling, linking, and switching the GL program, so do not
* call this in fast rendering loops.
*
* @param vertexShaderGlsl The vertex shader program.
* @param fragmentShaderGlsl The fragment shader program.
*/
public Program(String vertexShaderGlsl, String fragmentShaderGlsl) {
programId = GLES20.glCreateProgram();
checkGlError();
// Add the vertex and fragment shaders.
addShader(programId, GLES20.GL_VERTEX_SHADER, vertexShaderGlsl);
addShader(programId, GLES20.GL_FRAGMENT_SHADER, fragmentShaderGlsl);
// Link and use the program, and enumerate attributes/uniforms.
GLES20.glLinkProgram(programId);
int[] linkStatus = new int[] {GLES20.GL_FALSE};
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
throwGlException(
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
}
GLES20.glUseProgram(programId);
attributeByName = new HashMap<>();
int[] attributeCount = new int[1];
GLES20.glGetProgramiv(
programId, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, /* offset= */ 0);
attributes = new Attribute[attributeCount[0]];
for (int i = 0; i < attributeCount[0]; i++) {
Attribute attribute = Attribute.create(programId, i);
attributes[i] = attribute;
attributeByName.put(attribute.name, attribute);
}
uniformByName = new HashMap<>();
int[] uniformCount = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, /* offset= */ 0);
uniforms = new Uniform[uniformCount[0]];
for (int i = 0; i < uniformCount[0]; i++) {
Uniform uniform = Uniform.create(programId, i);
uniforms[i] = uniform;
uniformByName.put(uniform.name, uniform);
}
checkGlError();
}
/**
* Uses the program.
*
* <p>Call this in the rendering loop to switch between different programs.
*/
public void use() {
// TODO(http://b/205002913): When multiple GL programs are supported by Transformer, make sure
// to call use() to switch between programs.
GLES20.glUseProgram(programId);
checkGlError();
}
/** Deletes the program. Deleted programs cannot be used again. */
public void delete() {
GLES20.glDeleteProgram(programId);
checkGlError();
}
/**
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
* array.
*/
public int getAttributeArrayLocationAndEnable(String attributeName) {
int location = getAttributeLocation(attributeName);
GLES20.glEnableVertexAttribArray(location);
checkGlError();
return location;
}
/** Returns the location of an {@link Attribute}. */
private int getAttributeLocation(String attributeName) {
return GlUtil.getAttributeLocation(programId, attributeName);
}
/** Returns the location of a {@link Uniform}. */
public int getUniformLocation(String uniformName) {
return GlUtil.getUniformLocation(programId, uniformName);
}
/** Sets a float buffer type attribute. */
public void setBufferAttribute(String name, float[] values, int size) {
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
}
/** Sets a texture sampler type uniform. */
public void setSamplerTexIdUniform(String name, int texId, int unit) {
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, unit);
}
/** Sets a float type uniform. */
public void setFloatUniform(String name, float value) {
checkNotNull(uniformByName.get(name)).setFloat(value);
}
/** Sets a float array type uniform. */
public void setFloatsUniform(String name, float[] value) {
checkNotNull(uniformByName.get(name)).setFloats(value);
}
/** Binds all attributes and uniforms in the program. */
public void bindAttributesAndUniforms() {
for (Attribute attribute : attributes) {
attribute.bind();
}
for (Uniform uniform : uniforms) {
uniform.bind();
}
}
}
/** Whether to throw a {@link GlException} in case of an OpenGL error. */
public static boolean glAssertionsEnabled = false;
private static final String TAG = "GlUtil";
private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
/** Class only contains static methods. */
private GlUtil() {}
/**
* Returns whether creating a GL context with {@value #EXTENSION_PROTECTED_CONTENT} is possible.
* If {@code true}, the device supports a protected output path for DRM content when using GL.
*/
public static boolean isProtectedContentExtensionSupported(Context context) {
if (Util.SDK_INT < 24) {
return false;
}
if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
// Samsung devices running Nougat are known to be broken. See
// https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
// Moto Z XT1650 is also affected. See
// https://github.com/google/ExoPlayer/issues/3215.
return false;
}
if (Util.SDK_INT < 26
&& !context
.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
// Pre API level 26 devices were not well tested unless they supported VR mode.
return false;
}
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
return eglExtensions != null && eglExtensions.contains(EXTENSION_PROTECTED_CONTENT);
}
/**
* Returns whether creating a GL context with {@value #EXTENSION_SURFACELESS_CONTEXT} is possible.
*/
public static boolean isSurfacelessContextExtensionSupported() {
if (Util.SDK_INT < 17) {
return false;
}
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
return eglExtensions != null && eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT);
}
/** Returns an initialized default {@link EGLDisplay}. */
@RequiresApi(17)
public static EGLDisplay createEglDisplay() {
return Api17.createEglDisplay();
}
/** Returns a new {@link EGLContext} for the specified {@link EGLDisplay}. */
@RequiresApi(17)
public static EGLContext createEglContext(EGLDisplay eglDisplay) {
return Api17.createEglContext(eglDisplay);
}
/**
* Returns a new {@link EGLSurface} wrapping the specified {@code surface}.
*
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
*/
@RequiresApi(17)
public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) {
return Api17.getEglSurface(eglDisplay, surface);
}
/**
* If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
* a {@link GlException}.
*/
public static void checkGlError() {
int lastError = GLES20.GL_NO_ERROR;
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, "glError: " + gluErrorString(error));
lastError = error;
}
if (lastError != GLES20.GL_NO_ERROR) {
throwGlException("glError: " + gluErrorString(lastError));
}
}
/**
* Makes the specified {@code surface} the render target, using a viewport of {@code width} by
* {@code height} pixels.
*/
@RequiresApi(17)
public static void focusSurface(
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) {
Api17.focusSurface(eglDisplay, eglContext, surface, width, height);
}
/**
* Deletes a GL texture.
*
* @param textureId The ID of the texture to delete.
*/
public static void deleteTexture(int textureId) {
GLES20.glDeleteTextures(/* n= */ 1, new int[] {textureId}, /* offset= */ 0);
checkGlError();
}
/**
* Destroys the {@link EGLContext} identified by the provided {@link EGLDisplay} and {@link
* EGLContext}.
*/
@RequiresApi(17)
public static void destroyEglContext(
@Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) {
Api17.destroyEglContext(eglDisplay, eglContext);
}
/**
* Allocates a FloatBuffer with the given data.
*
* @param data Used to initialize the new buffer.
*/
public static FloatBuffer createBuffer(float[] data) {
return (FloatBuffer) createBuffer(data.length).put(data).flip();
}
/**
* Allocates a FloatBuffer.
*
* @param capacity The new buffer's capacity, in floats.
*/
public static FloatBuffer createBuffer(int capacity) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(capacity * C.BYTES_PER_FLOAT);
return byteBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
}
/**
* Loads a file from the assets folder.
*
* @param context The {@link Context}.
* @param assetPath The path to the file to load, from the assets folder.
* @return The content of the file to load.
* @throws IOException If the file couldn't be read.
*/
public static String loadAsset(Context context, String assetPath) throws IOException {
@Nullable InputStream inputStream = null;
try {
inputStream = context.getAssets().open(assetPath);
return Util.fromUtf8Bytes(Util.toByteArray(inputStream));
} finally {
Util.closeQuietly(inputStream);
}
}
/**
* Creates a GL_TEXTURE_EXTERNAL_OES with default configuration of GL_LINEAR filtering and
* GL_CLAMP_TO_EDGE wrapping.
*/
public static int createExternalTexture() {
int[] texId = new int[1];
GLES20.glGenTextures(/* n= */ 1, IntBuffer.wrap(texId));
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId[0]);
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
checkGlError();
return texId[0];
}
private static void addShader(int programId, int type, String glsl) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, glsl);
GLES20.glCompileShader(shader);
int[] result = new int[] {GLES20.GL_FALSE};
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
if (result[0] != GLES20.GL_TRUE) {
throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
}
GLES20.glAttachShader(programId, shader);
GLES20.glDeleteShader(shader);
checkGlError();
}
private static int getAttributeLocation(int programId, String attributeName) {
return GLES20.glGetAttribLocation(programId, attributeName);
}
private static int getUniformLocation(int programId, String uniformName) {
return GLES20.glGetUniformLocation(programId, uniformName);
}
private static void throwGlException(String errorMsg) {
Log.e(TAG, errorMsg);
if (glAssertionsEnabled) {
throw new GlException(errorMsg);
}
}
private static void checkEglException(boolean expression, String errorMessage) {
if (!expression) {
throwGlException(errorMessage);
}
}
/** Returns the length of the null-terminated string in {@code strVal}. */
private static int strlen(byte[] strVal) {
for (int i = 0; i < strVal.length; ++i) {
if (strVal[i] == '\0') {
return i;
}
}
return strVal.length;
}
/**
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
*/
private static final class Attribute {
/* Returns the attribute at the given index in the program. */
public static Attribute create(int programId, int index) {
int[] length = new int[1];
GLES20.glGetProgramiv(
programId, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, length, /* offset= */ 0);
byte[] nameBytes = new byte[length[0]];
GLES20.glGetActiveAttrib(
programId,
index,
length[0],
/* unusedLength */ new int[1],
/* lengthOffset= */ 0,
/* unusedSize */ new int[1],
/* sizeOffset= */ 0,
/* unusedType */ new int[1],
/* typeOffset= */ 0,
nameBytes,
/* nameOffset= */ 0);
String name = new String(nameBytes, /* offset= */ 0, strlen(nameBytes));
int location = getAttributeLocation(programId, name);
return new Attribute(name, index, location);
}
/** The name of the attribute in the GLSL sources. */
public final String name;
private final int index;
private final int location;
@Nullable private Buffer buffer;
private int size;
private Attribute(String name, int index, int location) {
this.name = name;
this.index = index;
this.location = location;
}
/**
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
* elements) to this {@link Attribute}.
*
* @param buffer Buffer to bind to this attribute.
* @param size Number of elements per vertex.
*/
public void setBuffer(float[] buffer, int size) {
this.buffer = createBuffer(buffer);
this.size = size;
}
/**
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
*
* <p>Should be called before each drawing call.
*/
public void bind() {
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
GLES20.glVertexAttribPointer(
location, size, GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, buffer);
GLES20.glEnableVertexAttribArray(index);
checkGlError();
}
}
/**
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
*/
private static final class Uniform {
/** Returns the uniform at the given index in the program. */
public static Uniform create(int programId, int index) {
int[] length = new int[1];
GLES20.glGetProgramiv(
programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, length, /* offset= */ 0);
int[] type = new int[1];
byte[] nameBytes = new byte[length[0]];
GLES20.glGetActiveUniform(
programId,
index,
length[0],
/* unusedLength */ new int[1],
/* lengthOffset= */ 0,
/* unusedSize */ new int[1],
/*sizeOffset= */ 0,
type,
/* typeOffset= */ 0,
nameBytes,
/* nameOffset= */ 0);
String name = new String(nameBytes, /* offset= */ 0, strlen(nameBytes));
int location = getUniformLocation(programId, name);
return new Uniform(name, location, type[0]);
}
/** The name of the uniform in the GLSL sources. */
public final String name;
private final int location;
private final int type;
private final float[] value;
private int texId;
private int unit;
private Uniform(String name, int location, int type) {
this.name = name;
this.location = location;
this.type = type;
this.value = new float[16];
}
/**
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
*
* @param texId The GL texture identifier from which to sample.
* @param unit The GL texture unit index.
*/
public void setSamplerTexId(int texId, int unit) {
this.texId = texId;
this.unit = unit;
}
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
public void setFloat(float value) {
this.value[0] = value;
}
/** Configures {@link #bind()} to use the specified float[] {@code value} for this uniform. */
public void setFloats(float[] value) {
System.arraycopy(value, /* srcPos= */ 0, this.value, /* destPos= */ 0, value.length);
}
/**
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)}, {@link
* #setFloat(float)} or {@link #setFloats(float[])}.
*
* <p>Should be called before each drawing call.
*/
public void bind() {
if (type == GLES20.GL_FLOAT) {
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
checkGlError();
return;
}
if (type == GLES20.GL_FLOAT_MAT4) {
GLES20.glUniformMatrix4fv(
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
checkGlError();
return;
}
if (texId == 0) {
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) {
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
} else if (type == GLES20.GL_SAMPLER_2D) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
} else {
throw new IllegalStateException("Unexpected uniform type: " + type);
}
GLES20.glUniform1i(location, unit);
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.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);
checkGlError();
}
}
@RequiresApi(17)
private static final class Api17 {
private Api17() {}
@DoNotInline
public static EGLDisplay createEglDisplay() {
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
checkEglException(!eglDisplay.equals(EGL14.EGL_NO_DISPLAY), "No EGL display.");
if (!EGL14.eglInitialize(
eglDisplay,
/* unusedMajor */ new int[1],
/* majorOffset= */ 0,
/* unusedMinor */ new int[1],
/* minorOffset= */ 0)) {
throwGlException("Error in eglInitialize.");
}
checkGlError();
return eglDisplay;
}
@DoNotInline
public static EGLContext createEglContext(EGLDisplay eglDisplay) {
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
EGLContext eglContext =
EGL14.eglCreateContext(
eglDisplay,
getEglConfig(eglDisplay),
EGL14.EGL_NO_CONTEXT,
contextAttributes,
/* offset= */ 0);
if (eglContext == null) {
EGL14.eglTerminate(eglDisplay);
throwGlException(
"eglCreateContext() failed to create a valid context. The device may not support EGL"
+ " version 2");
}
checkGlError();
return eglContext;
}
@DoNotInline
public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) {
return EGL14.eglCreateWindowSurface(
eglDisplay,
getEglConfig(eglDisplay),
surface,
new int[] {EGL14.EGL_NONE},
/* offset= */ 0);
}
@DoNotInline
public static void focusSurface(
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) {
int[] boundFrameBuffer = new int[1];
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFrameBuffer, /* offset= */ 0);
int defaultFrameBuffer = 0;
if (boundFrameBuffer[0] != defaultFrameBuffer) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, defaultFrameBuffer);
}
EGL14.eglMakeCurrent(eglDisplay, surface, surface, eglContext);
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
}
@DoNotInline
public static void destroyEglContext(
@Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) {
if (eglDisplay == null) {
return;
}
EGL14.eglMakeCurrent(
eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
int error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing context: " + error);
if (eglContext != null) {
EGL14.eglDestroyContext(eglDisplay, eglContext);
error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error destroying context: " + error);
}
EGL14.eglReleaseThread();
error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing thread: " + error);
EGL14.eglTerminate(eglDisplay);
error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error terminating display: " + error);
}
@DoNotInline
private static EGLConfig getEglConfig(EGLDisplay eglDisplay) {
int[] defaultConfiguration =
new int[] {
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, /* redSize= */ 8,
EGL14.EGL_GREEN_SIZE, /* greenSize= */ 8,
EGL14.EGL_BLUE_SIZE, /* blueSize= */ 8,
EGL14.EGL_ALPHA_SIZE, /* alphaSize= */ 8,
EGL14.EGL_DEPTH_SIZE, /* depthSize= */ 0,
EGL14.EGL_STENCIL_SIZE, /* stencilSize= */ 0,
EGL14.EGL_NONE
};
EGLConfig[] eglConfigs = new EGLConfig[1];
if (!EGL14.eglChooseConfig(
eglDisplay,
defaultConfiguration,
/* attrib_listOffset= */ 0,
eglConfigs,
/* configsOffset= */ 0,
/* config_size= */ 1,
/* unusedNumConfig */ new int[1],
/* num_configOffset= */ 0)) {
throwGlException("eglChooseConfig failed.");
}
return eglConfigs[0];
}
}
}