Merge "EGL cleanup tests"
diff --git a/tests/tests/media/src/android/media/cts/InputSurface.java b/tests/tests/media/src/android/media/cts/InputSurface.java
index 8d5c133..f23309d 100644
--- a/tests/tests/media/src/android/media/cts/InputSurface.java
+++ b/tests/tests/media/src/android/media/cts/InputSurface.java
@@ -38,7 +38,6 @@
private static final boolean VERBOSE = false;
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
- private static final int EGL_OPENGL_ES2_BIT = 4;
private EGLDisplay mEGLDisplay;
private EGLContext mEGLContext;
@@ -78,7 +77,7 @@
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
- EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_NONE
};
@@ -118,14 +117,10 @@
* Surface that was passed to our constructor.
*/
public void release() {
- if (EGL14.eglGetCurrentContext().equals(mEGLContext)) {
- // Clear the current context and surface to ensure they are discarded immediately.
- EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
- EGL14.EGL_NO_CONTEXT);
+ if (mEGLDisplay != null) {
+ EGL14.eglReleaseThread();
+ EGL14.eglTerminate(mEGLDisplay);
}
- EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
- EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
- //EGL14.eglTerminate(mEGLDisplay);
mSurface.release();
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
index 5274627..8a299e2 100644
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -18,23 +18,14 @@
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
-import android.opengl.GLES20;
-import android.opengl.GLES11Ext;
-import android.opengl.GLSurfaceView;
-import android.opengl.Matrix;
import android.util.Log;
import android.view.Surface;
-import java.nio.ByteBuffer;
-
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;
-import javax.microedition.khronos.opengles.GL;
-import javax.microedition.khronos.opengles.GL10;
-
/**
@@ -182,15 +173,9 @@
* Discard all resources held by this class, notably the EGL context.
*/
public void release() {
- if (mEGL != null) {
- if (mEGL.eglGetCurrentContext().equals(mEGLContext)) {
- // Clear the current context and surface to ensure they are discarded immediately.
- mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
- EGL10.EGL_NO_CONTEXT);
- }
- mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface);
- mEGL.eglDestroyContext(mEGLDisplay, mEGLContext);
- //mEGL.eglTerminate(mEGLDisplay);
+ if (mEGL != null && mEGLDisplay != null) {
+ mEGL.eglReleaseThread();
+ mEGL.eglTerminate(mEGLDisplay);
}
mSurface.release();
diff --git a/tests/tests/nativeopengl/libnativeopengltests/Android.mk b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
index c9e10c4..e2bb08a 100644
--- a/tests/tests/nativeopengl/libnativeopengltests/Android.mk
+++ b/tests/tests/nativeopengl/libnativeopengltests/Android.mk
@@ -34,7 +34,8 @@
LOCAL_SRC_FILES := \
register.cpp \
GLTestHelper.cpp \
- tests/GLTest_test.cpp
+ tests/GLTest_test.cpp \
+ tests/EGLCleanup_test.cpp
LOCAL_SHARED_LIBRARIES := libEGL \
libGLESv2 \
diff --git a/tests/tests/nativeopengl/standalone/jni/Android.mk b/tests/tests/nativeopengl/standalone/jni/Android.mk
index c23a1df..06a1436 100644
--- a/tests/tests/nativeopengl/standalone/jni/Android.mk
+++ b/tests/tests/nativeopengl/standalone/jni/Android.mk
@@ -41,7 +41,8 @@
LOCAL_MODULE := nativeopengltests
LOCAL_SRC_FILES := GLTestHelper.cpp \
register.cpp \
- tests/GLTest_test.cpp
+ tests/GLTest_test.cpp \
+ tests/EGLCleanup_test.cpp
LOCAL_SHARE_LIBRARIES := libgtest
diff --git a/tests/tests/nativeopengl/standalone/jni/tests/EGLCleanup_test.cpp b/tests/tests/nativeopengl/standalone/jni/tests/EGLCleanup_test.cpp
new file mode 100644
index 0000000..b5bd830
--- /dev/null
+++ b/tests/tests/nativeopengl/standalone/jni/tests/EGLCleanup_test.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2013 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.
+ */
+
+#define LOG_TAG "EGLCleanup"
+#include <utils/Log.h>
+#include <utils/Thread.h>
+#include <jni.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <gtest/gtest.h>
+
+#include <GLTestHelper.h>
+
+#include <pthread.h>
+
+
+namespace android {
+
+/**
+ * Tests EGL cleanup edge cases.
+ */
+class EGLCleanupTest : public ::testing::Test {
+protected:
+ EGLCleanupTest() {}
+
+ virtual void SetUp() {
+ // Termination of a terminated display is defined to be a no-op.
+ // Android uses a refcounted implementation, so terminate it a few
+ // times to make sure it's really dead. Without this, we might not
+ // get all the way into the driver eglTerminate implementation
+ // when we call eglTerminate.
+ EGLDisplay disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (disp != EGL_NO_DISPLAY) {
+ ALOGD("speculative terminate");
+ eglTerminate(disp);
+ eglTerminate(disp);
+ eglTerminate(disp);
+ }
+ }
+ virtual void TearDown() {}
+};
+
+/**
+ * Perform an operation and then start a new thread.
+ *
+ * The trick here is that some code may be helpfully releasing storage in
+ * pthread_key destructors. Those run after the thread returns out of the
+ * initial function, but before the thread fully exits. We want them to
+ * run concurrently with the next thread's initialization so we can confirm
+ * that the specified behavior of eglTerminate vs. eglInitialize holds.
+ */
+class ChainedThread {
+public:
+ enum TestType {
+ TEST_CORRECT,
+ TEST_NO_RELEASE_CURRENT
+ };
+
+ ChainedThread(TestType testType) : mEglDisplay(EGL_NO_DISPLAY),
+ mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT),
+ mTestType(testType), mIteration(0), mResult(true) {
+ pthread_mutex_init(&mLock, NULL);
+ pthread_cond_init(&mCond, NULL);
+ }
+ ~ChainedThread() {
+ // could get fancy and clean up the mutex
+ }
+
+ /* start here */
+ bool start() {
+ lock();
+ bool result = startThread_l();
+ unlock();
+ return result;
+ }
+
+ /* waits until test is done; when finished, call getResult() */
+ bool waitForEnd() {
+ lock();
+ int err = pthread_cond_wait(&mCond, &mLock);
+ if (err != 0) {
+ ALOGW("pthread_cond_wait failed: %d", err);
+ }
+ unlock();
+ return err == 0;
+ }
+
+ /* returns the result; true means success */
+ bool getResult() {
+ return mResult;
+ }
+
+private:
+ enum { MAX_ITERATIONS = 1000 };
+
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLContext mEglContext;
+
+ TestType mTestType;
+ int mIteration;
+ bool mResult;
+ pthread_mutex_t mLock;
+ pthread_cond_t mCond;
+
+ // Assertions set a flag in Java and return from the current method (which
+ // must be declared to return void). They do not throw a C++ exception.
+ //
+ // Because we're running in a separate thread, which is not attached to
+ // the VM, the assert macros don't help us much. We could attach to the
+ // VM (by linking to libdvm.so and calling a global function), but the
+ // assertions won't cause the test to stop, which makes them less valuable.
+ //
+ // So instead we just return a boolean out of functions that can fail.
+
+ /* call this to start the test */
+ bool startThread_l() {
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ pthread_t newThread;
+ int err = pthread_create(&newThread, &attr, ChainedThread::func,
+ (void*) this);
+ return (err == 0);
+ }
+
+ /* thread entry point */
+ static void* func(void* arg) {
+ ChainedThread* obj = static_cast<ChainedThread*>(arg);
+ obj->doWork();
+ return NULL;
+ }
+
+ bool lock() {
+ int err = pthread_mutex_lock(&mLock);
+ if (err != 0) {
+ ALOGW("pthread_mutex_lock failed: %d", err);
+ }
+ return err == 0;
+ }
+
+ bool unlock() {
+ int err = pthread_mutex_unlock(&mLock);
+ if (err != 0) {
+ ALOGW("pthread_mutex_unlock failed: %d", err);
+ }
+ return err == 0;
+ }
+
+ /* main worker */
+ void doWork() {
+ lock();
+
+ if ((mIteration % 25) == 0) {
+ ALOGD("iteration %d\n", mIteration);
+ }
+
+ mIteration++;
+ bool result = runTest_l();
+ if (!result) {
+ ALOGW("failed at iteration %d, stopping test", mIteration);
+ mResult = false;
+ mIteration = MAX_ITERATIONS;
+ }
+
+ if (mIteration < MAX_ITERATIONS) {
+ // still going, try to start the next one
+ if (!startThread_l()) {
+ ALOGW("Unable to start thread at iter=%d", mIteration);
+ mResult = false;
+ mIteration = MAX_ITERATIONS;
+ }
+ }
+
+ if (mIteration >= MAX_ITERATIONS) {
+ ALOGD("Test ending, signaling main thread");
+ pthread_cond_signal(&mCond);
+ }
+
+ unlock();
+ }
+
+ /* setup, use, release EGL */
+ bool runTest_l() {
+ if (!eglSetup()) {
+ return false;
+ }
+ if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext))
+ {
+ ALOGW("eglMakeCurrent failed: 0x%x", eglGetError());
+ return false;
+ }
+ if (!eglRelease_l()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
+ * Sets up EGL. Creates a 1280x720 pbuffer, which is large enough to
+ * cause a rapid and highly visible memory leak if we fail to discard it.
+ */
+ bool eglSetup() {
+ static const EGLint kConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_NONE
+ };
+ static const EGLint kContextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ static const EGLint kPbufferAttribs[] = {
+ EGL_WIDTH, 1280,
+ EGL_HEIGHT, 720,
+ EGL_NONE
+ };
+
+ //usleep(25000);
+
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (mEglDisplay == EGL_NO_DISPLAY) {
+ ALOGW("eglGetDisplay failed: 0x%x", eglGetError());
+ return false;
+ }
+
+ EGLint majorVersion, minorVersion;
+ if (!eglInitialize(mEglDisplay, &majorVersion, &minorVersion)) {
+ ALOGW("eglInitialize failed: 0x%x", eglGetError());
+ return false;
+ }
+
+ EGLConfig eglConfig;
+ EGLint numConfigs = 0;
+ if (!eglChooseConfig(mEglDisplay, kConfigAttribs, &eglConfig,
+ 1, &numConfigs)) {
+ ALOGW("eglChooseConfig failed: 0x%x", eglGetError());
+ return false;
+ }
+
+ mEglSurface = eglCreatePbufferSurface(mEglDisplay, eglConfig,
+ kPbufferAttribs);
+ if (mEglSurface == EGL_NO_SURFACE) {
+ ALOGW("eglCreatePbufferSurface failed: 0x%x", eglGetError());
+ return false;
+ }
+
+ mEglContext = eglCreateContext(mEglDisplay, eglConfig, EGL_NO_CONTEXT,
+ kContextAttribs);
+ if (mEglContext == EGL_NO_CONTEXT) {
+ ALOGW("eglCreateContext failed: 0x%x", eglGetError());
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
+ * Releases EGL. How we do that depends on the type of test we're
+ * running.
+ */
+ bool eglRelease_l() {
+ if (mEglDisplay == EGL_NO_DISPLAY) {
+ ALOGW("No display to release");
+ return false;
+ }
+
+ switch (mTestType) {
+ case TEST_CORRECT:
+ eglTerminate(mEglDisplay);
+ eglReleaseThread();
+ break;
+ case TEST_NO_RELEASE_CURRENT:
+ eglDestroyContext(mEglDisplay, mEglContext);
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ eglTerminate(mEglDisplay);
+ break;
+ default:
+ ALOGE("Unknown test type %d", mTestType);
+ break;
+ }
+
+ int err = eglGetError();
+ if (err != EGL_SUCCESS) {
+ ALOGW("eglRelease failed: 0x%x", err);
+ return false;
+ }
+ return true;
+ }
+};
+
+
+/* do things correctly */
+TEST_F(EGLCleanupTest, TestCorrect) {
+ ALOGI("Starting TEST_CORRECT");
+ ChainedThread cht(ChainedThread::TEST_CORRECT);
+
+ // start initial thread
+ ASSERT_TRUE(cht.start());
+
+ // wait for the end
+ cht.waitForEnd();
+ bool result = cht.getResult();
+ ASSERT_TRUE(result);
+}
+
+#if 0
+/* try it without un-currenting the surfaces and context */
+TEST_F(EGLCleanupTest, TestNoReleaseCurrent) {
+ ALOGI("Starting TEST_NO_RELEASE_CURRENT");
+ ChainedThread cht(ChainedThread::TEST_NO_RELEASE_CURRENT);
+
+ // start initial thread
+ ASSERT_TRUE(cht.start());
+
+ // wait for the end
+ cht.waitForEnd();
+ bool result = cht.getResult();
+ ASSERT_TRUE(result);
+}
+#endif
+
+} // namespace android
diff --git a/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java b/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java
index acc56fc..03677d2 100644
--- a/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/WrapperTest.java
@@ -93,7 +93,7 @@
// good
}
- eglRelease(true);
+ eglRelease();
}
/**
@@ -149,7 +149,7 @@
// good
}
- eglRelease(true);
+ eglRelease();
}
/**
@@ -174,7 +174,7 @@
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
- eglRelease(false);
+ eglRelease();
} catch (Throwable th) {
mThrowable = th;
}
@@ -281,18 +281,16 @@
}
/**
- * Releases EGL goodies. If switchCurrent is true, this will use eglMakeCurrent to switch
- * away from the current surface+context before destroying them.
+ * Releases EGL goodies.
*/
- private void eglRelease(boolean switchCurrent) {
- if (switchCurrent) {
- // Clear the current context and surface to ensure they are discarded immediately.
- EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
- EGL14.EGL_NO_CONTEXT);
+ private void eglRelease() {
+ // Terminating the display will release most objects, but won't discard the current
+ // surfaces and context until we release the thread. It shouldn't matter what order
+ // we do these in.
+ if (mEGLDisplay != null) {
+ EGL14.eglTerminate(mEGLDisplay);
+ EGL14.eglReleaseThread();
}
- EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
- EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
- //EGL14.eglTerminate(mEGLDisplay);
// null everything out so future attempts to use this object will cause an NPE
mEGLDisplay = null;