EGL Config Test

Bug 3184998

Get the EGL configurations that have the EGL_WINDOW_BIT enabled and try
to create a GLSurfaceView and draw a red or blue triangle on it...
if any of them crash then fail!

Change-Id: I88a13b268c136b0c24bed7f761632ee5ca5c8724
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 8cf1de2..7eaafb3 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -931,6 +931,8 @@
 
         <activity android:name="android.opengl.cts.OpenGlEsVersionStubActivity"/>
 
+        <activity android:name="android.opengl.cts.EglConfigStubActivity"/>
+
         <activity android:name="android.preference.cts.PreferenceStubActivity">
             <meta-data android:name="android.preference" 
                     android:resource="@xml/preferences_from_intent" />
diff --git a/tests/src/android/opengl/cts/EglConfigGLSurfaceView.java b/tests/src/android/opengl/cts/EglConfigGLSurfaceView.java
new file mode 100644
index 0000000..03e8d94
--- /dev/null
+++ b/tests/src/android/opengl/cts/EglConfigGLSurfaceView.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2011 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 android.opengl.cts;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * {@link GLSurfaceView} that uses the EGL configuration specified. Draws a couple frames of a red
+ * triangle and then calls the callback.
+ */
+public class EglConfigGLSurfaceView extends GLSurfaceView {
+
+    private static final String TAG = EglConfigGLSurfaceView.class.getName();
+
+    private final int mConfigId;
+
+    private final Runnable mCallback;
+
+    public EglConfigGLSurfaceView(Context context, int configId, int contextClientVersion,
+            Runnable callback) {
+        super(context);
+        mConfigId = configId;
+        mCallback = callback;
+        setEGLConfigChooser(new ConfigChooser());
+        setEGLContextClientVersion(contextClientVersion);
+        setRenderer(contextClientVersion == 1
+                ? new Renderer()
+                : new Renderer20());
+    }
+
+    private class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+            int[] attributeList = new int[] {
+                    EGL10.EGL_CONFIG_ID, mConfigId,
+                    EGL10.EGL_NONE
+            };
+
+            EGLConfig[] configs = new EGLConfig[1];
+            if (egl.eglChooseConfig(display, attributeList, configs, 1, new int[] {1})) {
+                // Print out the configuration since we may crash...
+                printConfig(egl, display, configs[0]);
+                return configs[0];
+            } else {
+                throw new IllegalStateException("Could not get EGL config...");
+            }
+        }
+    }
+
+    private class Renderer implements GLSurfaceView.Renderer {
+
+        private int mNumFrames;
+
+        private FloatBuffer mFloatBuffer;
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            float[] triangleVertices = {
+                    0.0f, 1.0f, -1.0f,
+                    -1.0f, -1.0f, -1.0f,
+                    1.0f, -1.0f, -1.0f
+            };
+
+            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(triangleVertices.length * 4);
+            byteBuffer.order(ByteOrder.nativeOrder());
+
+            mFloatBuffer = ByteBuffer.allocateDirect(triangleVertices.length * 4)
+                    .order(ByteOrder.nativeOrder())
+                    .asFloatBuffer();
+            mFloatBuffer.put(triangleVertices).position(0);
+
+            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+        }
+
+        public void onDrawFrame(GL10 gl) {
+            gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
+            gl.glColor4f(1.0f, 0, 0, 0);
+            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFloatBuffer);
+            gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 9);
+
+            if (++mNumFrames == 10) {
+                post(mCallback);
+            }
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            gl.glViewport(0, 0, width, height);
+        }
+    }
+
+    private class Renderer20 implements GLSurfaceView.Renderer {
+
+        private FloatBuffer mFloatBuffer;
+
+        private final String mVertexShader =
+            "attribute vec4 aPosition;\n" +
+            "void main() {\n" +
+            "  gl_Position = aPosition;\n" +
+            "}\n";
+
+        private final String mFragmentShader =
+            "void main() {\n" +
+            "  gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n" +
+            "}\n";
+
+        private int mProgram;
+
+        private int maPositionHandle;
+
+        private int mNumFrames;
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            mProgram = createProgram(mVertexShader, mFragmentShader);
+            if (mProgram == 0) {
+                throw new RuntimeException("Could not create program");
+            }
+
+            maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+            checkGlError("glGetAttribLocation aPosition");
+            if (maPositionHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for aPosition");
+            }
+
+            float[] triangleVertices = {
+                    0.0f, 1.0f, 0.0f,
+                    -1.0f, -1.0f, 0.0f,
+                    1.0f, -1.0f, 0.0f
+            };
+
+            mFloatBuffer = ByteBuffer.allocateDirect(triangleVertices.length * 4)
+                    .order(ByteOrder.nativeOrder())
+                    .asFloatBuffer();
+            mFloatBuffer.put(triangleVertices).position(0);
+        }
+
+        private 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();
+            if (program != 0) {
+                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;
+        }
+
+        private int loadShader(int shaderType, String source) {
+            int shader = GLES20.glCreateShader(shaderType);
+            if (shader != 0) {
+                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;
+        }
+
+        private void checkGlError(String op) {
+            int error;
+            while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+                Log.e(TAG, op + ": glError " + error);
+                throw new RuntimeException(op + ": glError " + error);
+            }
+        }
+
+        @Override
+        public void onDrawFrame(GL10 gl) {
+            GLES20.glClearColor(0, 0, 0, 1);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+            GLES20.glUseProgram(mProgram);
+
+            GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+                    0, mFloatBuffer);
+            checkGlError("glVertexAttribPointer maPosition");
+
+            GLES20.glEnableVertexAttribArray(maPositionHandle);
+            checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
+            checkGlError("glDrawArrays");
+
+            if (++mNumFrames == 10) {
+                post(mCallback);
+            }
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLES20.glViewport(0, 0, width, height);
+        }
+    }
+
+    /** Ripped from the NDK sample GL2JNIView class. */
+    private static void printConfig(EGL10 egl, EGLDisplay display,
+            EGLConfig config) {
+        int[] attributes = {
+                EGL10.EGL_BUFFER_SIZE,
+                EGL10.EGL_ALPHA_SIZE,
+                EGL10.EGL_BLUE_SIZE,
+                EGL10.EGL_GREEN_SIZE,
+                EGL10.EGL_RED_SIZE,
+                EGL10.EGL_DEPTH_SIZE,
+                EGL10.EGL_STENCIL_SIZE,
+                EGL10.EGL_CONFIG_CAVEAT,
+                EGL10.EGL_CONFIG_ID,
+                EGL10.EGL_LEVEL,
+                EGL10.EGL_MAX_PBUFFER_HEIGHT,
+                EGL10.EGL_MAX_PBUFFER_PIXELS,
+                EGL10.EGL_MAX_PBUFFER_WIDTH,
+                EGL10.EGL_NATIVE_RENDERABLE,
+                EGL10.EGL_NATIVE_VISUAL_ID,
+                EGL10.EGL_NATIVE_VISUAL_TYPE,
+                0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+                EGL10.EGL_SAMPLES,
+                EGL10.EGL_SAMPLE_BUFFERS,
+                EGL10.EGL_SURFACE_TYPE,
+                EGL10.EGL_TRANSPARENT_TYPE,
+                EGL10.EGL_TRANSPARENT_RED_VALUE,
+                EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+                EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+                0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+                0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+                0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+                0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+                EGL10.EGL_LUMINANCE_SIZE,
+                EGL10.EGL_ALPHA_MASK_SIZE,
+                EGL10.EGL_COLOR_BUFFER_TYPE,
+                EGL10.EGL_RENDERABLE_TYPE,
+                0x3042 // EGL10.EGL_CONFORMANT
+        };
+        String[] names = {
+                "EGL_BUFFER_SIZE",
+                "EGL_ALPHA_SIZE",
+                "EGL_BLUE_SIZE",
+                "EGL_GREEN_SIZE",
+                "EGL_RED_SIZE",
+                "EGL_DEPTH_SIZE",
+                "EGL_STENCIL_SIZE",
+                "EGL_CONFIG_CAVEAT",
+                "EGL_CONFIG_ID",
+                "EGL_LEVEL",
+                "EGL_MAX_PBUFFER_HEIGHT",
+                "EGL_MAX_PBUFFER_PIXELS",
+                "EGL_MAX_PBUFFER_WIDTH",
+                "EGL_NATIVE_RENDERABLE",
+                "EGL_NATIVE_VISUAL_ID",
+                "EGL_NATIVE_VISUAL_TYPE",
+                "EGL_PRESERVED_RESOURCES",
+                "EGL_SAMPLES",
+                "EGL_SAMPLE_BUFFERS",
+                "EGL_SURFACE_TYPE",
+                "EGL_TRANSPARENT_TYPE",
+                "EGL_TRANSPARENT_RED_VALUE",
+                "EGL_TRANSPARENT_GREEN_VALUE",
+                "EGL_TRANSPARENT_BLUE_VALUE",
+                "EGL_BIND_TO_TEXTURE_RGB",
+                "EGL_BIND_TO_TEXTURE_RGBA",
+                "EGL_MIN_SWAP_INTERVAL",
+                "EGL_MAX_SWAP_INTERVAL",
+                "EGL_LUMINANCE_SIZE",
+                "EGL_ALPHA_MASK_SIZE",
+                "EGL_COLOR_BUFFER_TYPE",
+                "EGL_RENDERABLE_TYPE",
+                "EGL_CONFORMANT"
+        };
+        int[] value = new int[1];
+        for (int i = 0; i < attributes.length; i++) {
+            int attribute = attributes[i];
+            String name = names[i];
+            if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
+                Log.w(TAG, String.format("  %s: %d\n", name, value[0]));
+            } else {
+                // Log.w(TAG, String.format("  %s: failed\n", name));
+                while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+            }
+        }
+    }
+}
diff --git a/tests/src/android/opengl/cts/EglConfigStubActivity.java b/tests/src/android/opengl/cts/EglConfigStubActivity.java
new file mode 100644
index 0000000..ab1a6d0
--- /dev/null
+++ b/tests/src/android/opengl/cts/EglConfigStubActivity.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 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 android.opengl.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@link Activity} with a {@link GLSurfaceView} that chooses a specific configuration.
+ */
+public class EglConfigStubActivity extends Activity {
+
+    public static final String CONFIG_ID_EXTRA = "eglConfigId";
+
+    public static final String CONTEXT_CLIENT_VERSION_EXTRA = "eglContextClientVersion";
+
+    private EglConfigGLSurfaceView mView;
+
+    private CountDownLatch mFinishedDrawing;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        int configId = getConfigId();
+        int contextClientVersion = getContextClientVersion();
+        setTitle("EGL Config Id: " + configId + " Client Version: " + contextClientVersion);
+
+        mFinishedDrawing = new CountDownLatch(1);
+        mView = new EglConfigGLSurfaceView(this, configId, contextClientVersion, new Runnable() {
+            @Override
+            public void run() {
+                mFinishedDrawing.countDown();
+            }
+        });
+        setContentView(mView);
+    }
+
+    private int getConfigId() {
+        Intent intent = getIntent();
+        if (intent != null) {
+            return intent.getIntExtra(CONFIG_ID_EXTRA, 0);
+        } else {
+            return 0;
+        }
+    }
+
+    private int getContextClientVersion() {
+        Intent intent = getIntent();
+        if (intent != null) {
+            return intent.getIntExtra(CONTEXT_CLIENT_VERSION_EXTRA, 0);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    public void waitToFinishDrawing() throws InterruptedException {
+        if (!mFinishedDrawing.await(3, TimeUnit.SECONDS)) {
+            throw new IllegalStateException("Coudn't finish drawing frames!");
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
new file mode 100644
index 0000000..2f918db
--- /dev/null
+++ b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 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 android.opengl.cts;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.test.ActivityInstrumentationTestCase2;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+
+/**
+ * Test that gets a list of EGL configurations and tries to use each one in a GLSurfaceView.
+ */
+public class EglConfigTest extends ActivityInstrumentationTestCase2<EglConfigStubActivity> {
+
+    private static final int EGL_OPENGL_ES_BIT = 0x1;
+
+    private static final int EGL_OPENGL_ES2_BIT = 0x4;
+
+    private Instrumentation mInstrumentation;
+
+    public EglConfigTest() {
+        super("com.android.cts.stub", EglConfigStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = getInstrumentation();
+    }
+
+    public void testEglConfigs() throws Exception {
+        int[] configIds = getEglConfigIds(EGL_OPENGL_ES_BIT);
+        int[] configIds2 = getEglConfigIds(EGL_OPENGL_ES2_BIT);
+        assertTrue(configIds.length + configIds2.length > 0);
+        runConfigTests(configIds, 1);
+        runConfigTests(configIds2, 2);
+    }
+
+    private void runConfigTests(int[] configIds, int contextClientVersion)
+            throws InterruptedException {
+        for (int configId : configIds) {
+            Bundle extras = new Bundle();
+            extras.putInt(EglConfigStubActivity.CONFIG_ID_EXTRA, configId);
+            extras.putInt(EglConfigStubActivity.CONTEXT_CLIENT_VERSION_EXTRA, contextClientVersion);
+            EglConfigStubActivity activity = launchActivity("com.android.cts.stub",
+                    EglConfigStubActivity.class, extras);
+            activity.waitToFinishDrawing();
+            activity.finish();
+            mInstrumentation.waitForIdleSync();
+        }
+    }
+
+    private static int[] getEglConfigIds(int renderableType) {
+        EGL10 egl = (EGL10) EGLContext.getEGL();
+        EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        int[] numConfigs = new int[1];
+
+        int[] attributeList = new int[] {
+                EGL10.EGL_RENDERABLE_TYPE, renderableType,
+
+                // Avoid configs like RGBA0008 which crash even though they have the window bit set.
+                EGL10.EGL_RED_SIZE, 1,
+                EGL10.EGL_GREEN_SIZE, 1,
+                EGL10.EGL_BLUE_SIZE, 1,
+
+                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
+                EGL10.EGL_NONE
+        };
+
+        if (egl.eglInitialize(display, null)) {
+            try {
+                if (egl.eglChooseConfig(display, attributeList, null, 0, numConfigs)) {
+                    EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+                    if (egl.eglChooseConfig(display, attributeList, configs, configs.length,
+                            numConfigs)) {
+                        int[] configIds = new int[numConfigs[0]];
+                        for (int i = 0; i < numConfigs[0]; i++) {
+                            int[] value = new int[1];
+                            if (egl.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_ID,
+                                    value)) {
+                                configIds[i] = value[0];
+                            } else {
+                              throw new IllegalStateException("Couldn't call eglGetConfigAttrib");
+                            }
+                        }
+                        return configIds;
+                    } else {
+                        throw new IllegalStateException("Couldn't call eglChooseConfig (1)");
+                    }
+                } else {
+                    throw new IllegalStateException("Couldn't call eglChooseConfig (2)");
+                }
+            } finally {
+                egl.eglTerminate(display);
+            }
+        } else {
+            throw new IllegalStateException("Couldn't initialize EGL.");
+        }
+    }
+}