1 /*
2  * Copyright 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "EGLCleanup"
18 #include <android/log.h>
19 #include <jni.h>
20 
21 #include <EGL/egl.h>
22 #include <EGL/eglext.h>
23 
24 #include <gtest/gtest.h>
25 
26 #include <GLTestHelper.h>
27 
28 #include <pthread.h>
29 
30 #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
31 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
32 #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
33 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
34 
35 namespace android {
36 
37 /**
38  * Tests EGL cleanup edge cases.
39  */
40 class EGLCleanupTest : public ::testing::Test {
41 protected:
EGLCleanupTest()42     EGLCleanupTest() {}
43 
SetUp()44     virtual void SetUp() {
45         // Termination of a terminated display is defined to be a no-op.
46         // Android uses a refcounted implementation, so terminate it a few
47         // times to make sure it's really dead.  Without this, we might not
48         // get all the way into the driver eglTerminate implementation
49         // when we call eglTerminate.
50         EGLDisplay disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
51         if (disp != EGL_NO_DISPLAY) {
52             ALOGD("speculative terminate");
53             eglTerminate(disp);
54             eglTerminate(disp);
55             eglTerminate(disp);
56         }
57     }
TearDown()58     virtual void TearDown() {}
59 };
60 
61 /**
62  * Perform an operation and then start a new thread.
63  *
64  * The trick here is that some code may be helpfully releasing storage in
65  * pthread_key destructors.  Those run after the thread returns out of the
66  * initial function, but before the thread fully exits.  We want them to
67  * run concurrently with the next thread's initialization so we can confirm
68  * that the specified behavior of eglTerminate vs. eglInitialize holds.
69  */
70 class ChainedThread {
71 public:
72     enum TestType {
73         TEST_CORRECT,
74         TEST_NO_RELEASE_CURRENT
75     };
76 
ChainedThread(TestType testType)77     ChainedThread(TestType testType) : mEglDisplay(EGL_NO_DISPLAY),
78             mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT),
79             mTestType(testType), mIteration(0), mResult(true) {
80         pthread_mutex_init(&mLock, NULL);
81         pthread_cond_init(&mCond, NULL);
82     }
~ChainedThread()83     ~ChainedThread() {
84         // could get fancy and clean up the mutex
85     }
86 
87     /* start here */
start()88     bool start() {
89         lock();
90         bool result = startThread_l();
91         unlock();
92         return result;
93     }
94 
95     /* waits until test is done; when finished, call getResult() */
waitForEnd()96     bool waitForEnd() {
97         lock();
98         int err = pthread_cond_wait(&mCond, &mLock);
99         if (err != 0) {
100             ALOGW("pthread_cond_wait failed: %d", err);
101         }
102         unlock();
103         return err == 0;
104     }
105 
106     /* returns the result; true means success */
getResult()107     bool getResult() {
108         return mResult;
109     }
110 
111 private:
112     enum { MAX_ITERATIONS = 1000 };
113 
114     EGLDisplay mEglDisplay;
115     EGLSurface mEglSurface;
116     EGLContext mEglContext;
117 
118     TestType mTestType;
119     int mIteration;
120     bool mResult;
121     pthread_mutex_t mLock;
122     pthread_cond_t mCond;
123 
124     // Assertions set a flag in Java and return from the current method (which
125     // must be declared to return void).  They do not throw a C++ exception.
126     //
127     // Because we're running in a separate thread, which is not attached to
128     // the VM, the assert macros don't help us much.  We could attach to the
129     // VM (by linking to libdvm.so and calling a global function), but the
130     // assertions won't cause the test to stop, which makes them less valuable.
131     //
132     // So instead we just return a boolean out of functions that can fail.
133 
134     /* call this to start the test */
startThread_l()135     bool startThread_l() {
136         pthread_attr_t attr;
137         pthread_attr_init(&attr);
138         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
139 
140         pthread_t newThread;
141         int err = pthread_create(&newThread, &attr, ChainedThread::func,
142                 (void*) this);
143         return (err == 0);
144     }
145 
146     /* thread entry point */
func(void * arg)147     static void* func(void* arg) {
148         ChainedThread* obj = static_cast<ChainedThread*>(arg);
149         obj->doWork();
150         return NULL;
151     }
152 
lock()153     bool lock() {
154         int err = pthread_mutex_lock(&mLock);
155         if (err != 0) {
156             ALOGW("pthread_mutex_lock failed: %d", err);
157         }
158         return err == 0;
159     }
160 
unlock()161     bool unlock() {
162         int err = pthread_mutex_unlock(&mLock);
163         if (err != 0) {
164             ALOGW("pthread_mutex_unlock failed: %d", err);
165         }
166         return err == 0;
167     }
168 
169     /* main worker */
doWork()170     void doWork() {
171         lock();
172 
173         if ((mIteration % 25) == 0) {
174             ALOGD("iteration %d\n", mIteration);
175         }
176 
177         mIteration++;
178         bool result = runTest_l();
179         if (!result) {
180             ALOGW("failed at iteration %d, stopping test", mIteration);
181             mResult = false;
182             mIteration = MAX_ITERATIONS;
183         }
184 
185         if (mIteration < MAX_ITERATIONS) {
186             // still going, try to start the next one
187             if (!startThread_l()) {
188                 ALOGW("Unable to start thread at iter=%d", mIteration);
189                 mResult = false;
190                 mIteration = MAX_ITERATIONS;
191             }
192         }
193 
194         if (mIteration >= MAX_ITERATIONS) {
195             ALOGD("Test ending, signaling main thread");
196             pthread_cond_signal(&mCond);
197         }
198 
199         unlock();
200     }
201 
202     /* setup, use, release EGL */
runTest_l()203     bool runTest_l() {
204         if (!eglSetup()) {
205             return false;
206         }
207         if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
208                 mEglContext))
209         {
210             ALOGW("eglMakeCurrent failed: 0x%x", eglGetError());
211             return false;
212         }
213         if (!eglRelease_l()) {
214             return false;
215         }
216 
217         return true;
218     }
219 
220     /*
221      * Sets up EGL.  Creates a 1280x720 pbuffer, which is large enough to
222      * cause a rapid and highly visible memory leak if we fail to discard it.
223      */
eglSetup()224     bool eglSetup() {
225         static const EGLint kConfigAttribs[] = {
226                 EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
227                 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
228                 EGL_RED_SIZE, 8,
229                 EGL_GREEN_SIZE, 8,
230                 EGL_BLUE_SIZE, 8,
231                 EGL_NONE
232         };
233         static const EGLint kContextAttribs[] = {
234                 EGL_CONTEXT_CLIENT_VERSION, 2,
235                 EGL_NONE
236         };
237         static const EGLint kPbufferAttribs[] = {
238                 EGL_WIDTH, 1280,
239                 EGL_HEIGHT, 720,
240                 EGL_NONE
241         };
242 
243         //usleep(25000);
244 
245         mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
246         if (mEglDisplay == EGL_NO_DISPLAY) {
247             ALOGW("eglGetDisplay failed: 0x%x", eglGetError());
248             return false;
249         }
250 
251         EGLint majorVersion, minorVersion;
252         if (!eglInitialize(mEglDisplay, &majorVersion, &minorVersion)) {
253             ALOGW("eglInitialize failed: 0x%x", eglGetError());
254             return false;
255         }
256 
257         EGLConfig eglConfig;
258         EGLint numConfigs = 0;
259         if (!eglChooseConfig(mEglDisplay, kConfigAttribs, &eglConfig,
260                 1, &numConfigs)) {
261             ALOGW("eglChooseConfig failed: 0x%x", eglGetError());
262             return false;
263         }
264 
265         mEglSurface = eglCreatePbufferSurface(mEglDisplay, eglConfig,
266                 kPbufferAttribs);
267         if (mEglSurface == EGL_NO_SURFACE) {
268             ALOGW("eglCreatePbufferSurface failed: 0x%x", eglGetError());
269             return false;
270         }
271 
272         mEglContext = eglCreateContext(mEglDisplay, eglConfig, EGL_NO_CONTEXT,
273                 kContextAttribs);
274         if (mEglContext == EGL_NO_CONTEXT) {
275             ALOGW("eglCreateContext failed: 0x%x", eglGetError());
276             return false;
277         }
278 
279         return true;
280     }
281 
282     /*
283      * Releases EGL.  How we do that depends on the type of test we're
284      * running.
285      */
eglRelease_l()286     bool eglRelease_l() {
287         if (mEglDisplay == EGL_NO_DISPLAY) {
288             ALOGW("No display to release");
289             return false;
290         }
291 
292         switch (mTestType) {
293         case TEST_CORRECT:
294             eglTerminate(mEglDisplay);
295             eglReleaseThread();
296             break;
297         case TEST_NO_RELEASE_CURRENT:
298             eglDestroyContext(mEglDisplay, mEglContext);
299             eglDestroySurface(mEglDisplay, mEglSurface);
300             eglTerminate(mEglDisplay);
301             break;
302         default:
303             ALOGE("Unknown test type %d", mTestType);
304             break;
305         }
306 
307         int err = eglGetError();
308         if (err != EGL_SUCCESS) {
309             ALOGW("eglRelease failed: 0x%x", err);
310             return false;
311         }
312         return true;
313     }
314 };
315 
316 
317 /* do things correctly */
TEST_F(EGLCleanupTest,TestCorrect)318 TEST_F(EGLCleanupTest, TestCorrect) {
319     ALOGI("Starting TEST_CORRECT");
320     ChainedThread cht(ChainedThread::TEST_CORRECT);
321 
322     // start initial thread
323     ASSERT_TRUE(cht.start());
324 
325     // wait for the end
326     cht.waitForEnd();
327     bool result = cht.getResult();
328     ASSERT_TRUE(result);
329 }
330 
331 /* try it without un-currenting the surfaces and context
332 TEST _F(EGLCleanupTest, TestNoReleaseCurrent) {
333     ALOGI("Starting TEST_NO_RELEASE_CURRENT");
334     ChainedThread cht(ChainedThread::TEST_NO_RELEASE_CURRENT);
335 
336     // start initial thread
337     ASSERT_TRUE(cht.start());
338 
339     // wait for the end
340     cht.waitForEnd();
341     bool result = cht.getResult();
342     ASSERT_TRUE(result);
343 }
344 */
345 
346 } // namespace android
347