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