1 /* 2 * Copyright (C) 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 package android.opengl.cts; 18 19 import android.opengl.EGL14; 20 import android.opengl.EGLConfig; 21 import android.opengl.EGLContext; 22 import android.opengl.EGLDisplay; 23 import android.opengl.EGLSurface; 24 import android.opengl.GLES10; 25 import android.opengl.GLES20; 26 import android.test.AndroidTestCase; 27 import android.util.Log; 28 29 import java.nio.ByteBuffer; 30 import java.nio.IntBuffer; 31 32 /** 33 * Test some aspects of the Java-language wrappers generated for OpenGL. 34 */ 35 public class WrapperTest extends AndroidTestCase { 36 private static final String TAG = "WrapperTest"; 37 38 private EGLDisplay mEGLDisplay; 39 private EGLContext mEGLContext; 40 private EGLSurface mEGLSurface; 41 42 43 /** 44 * Tests range-checking on glGetIntegerv in GLES 1.x. 45 */ 46 public void testGetIntegerv1() { 47 eglSetup(1, 1, 1); // GLES 1.x with 1x1 pbuffer 48 49 checkGlError("start"); 50 51 int[] countBuf = new int[1]; 52 GLES10.glGetIntegerv(GLES10.GL_NUM_COMPRESSED_TEXTURE_FORMATS, countBuf, 0); 53 checkGlError("glGetIntegerv(count)"); 54 55 int formatCount = countBuf[0]; 56 Log.d(TAG, "got count=" + formatCount); 57 58 // try with a buffer large enough to hold all results 59 GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount], 0); 60 checkGlError("glGetIntegerv(full1)"); 61 62 // try with an exact-fit IntBuffer 63 ByteBuffer fullByteBuf = ByteBuffer.allocateDirect(4 * formatCount); 64 IntBuffer fullIntBuf = fullByteBuf.asIntBuffer(); 65 GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, fullIntBuf); 66 checkGlError("glGetIntegerv(full2)"); 67 68 // try with an oversized IntBuffer with an offset 69 final int OFFSET = 5; 70 ByteBuffer oversizeByteBuf = ByteBuffer.allocateDirect(4 * (formatCount+OFFSET)); 71 IntBuffer oversizeIntBuf = oversizeByteBuf.asIntBuffer(); 72 oversizeIntBuf.position(OFFSET); 73 GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, oversizeIntBuf); 74 checkGlError("glGetIntegerv(full3)"); 75 assertEquals(oversizeIntBuf.get(OFFSET), fullIntBuf.get(0)); 76 77 // retry with a buffer that's too small -- should throw 78 ByteBuffer partialByteBuf = ByteBuffer.allocateDirect(4 * (formatCount - 1)); 79 IntBuffer partialIntBuf = partialByteBuf.asIntBuffer(); 80 try { 81 GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, partialIntBuf); 82 checkGlError("glGetIntegerv(partial1)"); 83 throw new RuntimeException("buffer has overrun (intbuf)"); 84 } catch (IllegalArgumentException iae) { 85 // good 86 } 87 88 try { 89 GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount-1], 0); 90 checkGlError("glGetIntegerv(partial2)"); 91 throw new RuntimeException("buffer has overrun (int[])"); 92 } catch (IllegalArgumentException iae) { 93 // good 94 } 95 96 eglRelease(); 97 } 98 99 /** 100 * Tests range-checking on glGetIntegerv in GLES 2.x. 101 */ 102 public void testGetIntegerv2() { 103 eglSetup(2, 1, 1); // GLES 2.x with 1x1 pbuffer 104 105 checkGlError("start"); 106 107 int[] countBuf = new int[1]; 108 GLES20.glGetIntegerv(GLES20.GL_NUM_COMPRESSED_TEXTURE_FORMATS, countBuf, 0); 109 checkGlError("glGetIntegerv(count)"); 110 111 int formatCount = countBuf[0]; 112 Log.d(TAG, "got count=" + formatCount); 113 114 // try with a buffer large enough to hold all results 115 GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount], 0); 116 checkGlError("glGetIntegerv(full1)"); 117 118 // try with an exact-fit IntBuffer 119 ByteBuffer fullByteBuf = ByteBuffer.allocateDirect(4 * formatCount); 120 IntBuffer fullIntBuf = fullByteBuf.asIntBuffer(); 121 GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, fullIntBuf); 122 checkGlError("glGetIntegerv(full2)"); 123 124 // try with an oversized IntBuffer with an offset 125 final int OFFSET = 5; 126 ByteBuffer oversizeByteBuf = ByteBuffer.allocateDirect(4 * (formatCount+OFFSET)); 127 IntBuffer oversizeIntBuf = oversizeByteBuf.asIntBuffer(); 128 oversizeIntBuf.position(OFFSET); 129 GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, oversizeIntBuf); 130 checkGlError("glGetIntegerv(full3)"); 131 assertEquals(oversizeIntBuf.get(OFFSET), fullIntBuf.get(0)); 132 133 // retry with a buffer that's too small -- should throw 134 ByteBuffer partialByteBuf = ByteBuffer.allocateDirect(4 * (formatCount - 1)); 135 IntBuffer partialIntBuf = partialByteBuf.asIntBuffer(); 136 try { 137 GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, partialIntBuf); 138 checkGlError("glGetIntegerv(partial1)"); 139 throw new RuntimeException("buffer has overrun (intbuf)"); 140 } catch (IllegalArgumentException iae) { 141 // good 142 } 143 144 try { 145 GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount-1], 0); 146 checkGlError("glGetIntegerv(partial2)"); 147 throw new RuntimeException("buffer has overrun (int[])"); 148 } catch (IllegalArgumentException iae) { 149 // good 150 } 151 152 eglRelease(); 153 } 154 155 /** 156 * Tests whether EGL is releasing resources when the thread exits. If 157 * it doesn't, we'll consume memory rapidly, and will fail or be 158 * killed within a couple hundred iterations. 159 * <p> 160 * It may be worthwhile to watch the memory growth with procrank or showmap 161 * while the test runs to detect smaller leaks. 162 */ 163 public void testThreadCleanup() throws Throwable { 164 class WrappedTest implements Runnable { 165 public Throwable mThrowable; 166 167 private static final int WIDTH = 1280; 168 private static final int HEIGHT = 720; 169 170 @Override 171 public void run() { 172 try { 173 eglSetup(2, WIDTH, HEIGHT); 174 if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 175 throw new RuntimeException("eglMakeCurrent failed"); 176 } 177 eglRelease(); 178 } catch (Throwable th) { 179 mThrowable = th; 180 } 181 } 182 } 183 184 WrappedTest wrappedTest = new WrappedTest(); 185 186 // Android has "reference-counted" EGL initialization. We want our eglTerminate call 187 // to be the "last" termination, since that's the situation we're trying to test, but 188 // it's possible that some previous test failed to balance eglInitialize and 189 // eglTerminate. So we call eglTerminate several times in a desperate attempt to 190 // zero out the refcount. 191 // 192 // Before we can terminate we need to be sure that the display has been initialized 193 // at least once so call eglSetup first. 194 // 195 // IMPORTANT NOTE: If a previous test in this test group fails to cleanup its 196 // GLSurfaceView there may be a crash here on some platforms due to an object destruction 197 // race condition. The solution is to make sure all previous tests override onPause() 198 // and call their GLSurfaceView's onPause() function there. See b/37118199 for history. 199 eglSetup(2, 1, 1); 200 for (int i = 0; i < 100; i++) { 201 EGL14.eglTerminate(mEGLDisplay); 202 } 203 204 for (int i = 0; i < 1000; i++) { 205 if ((i % 25) == 0) { 206 Log.d(TAG, "iteration " + i); 207 } 208 209 Thread th = new Thread(wrappedTest, "EGL thrash"); 210 th.start(); 211 th.join(); 212 if (wrappedTest.mThrowable != null) { 213 throw wrappedTest.mThrowable; 214 } 215 } 216 } 217 218 /** 219 * Checks for GL errors. 220 */ 221 public void checkGlError(String op) { 222 int error; 223 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 224 Log.e(TAG, op + ": glError " + error); 225 throw new RuntimeException(op + ": glError " + error); 226 } 227 } 228 229 /** 230 * Prepares EGL. Pass in the desired GLES API version (1 or 2). 231 * <p> 232 * Sets mEGLDisplay, mEGLContext, and mEGLSurface, and makes them current. 233 */ 234 private void eglSetup(int api, int width, int height) { 235 mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 236 if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 237 throw new RuntimeException("unable to get EGL14 display"); 238 } 239 int[] version = new int[2]; 240 if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 241 mEGLDisplay = null; 242 throw new RuntimeException("unable to initialize EGL14"); 243 } 244 245 int renderableType; 246 switch (api) { 247 case 1: 248 renderableType = EGL14.EGL_OPENGL_ES_BIT; 249 break; 250 case 2: 251 renderableType = EGL14.EGL_OPENGL_ES2_BIT; 252 break; 253 default: 254 throw new RuntimeException("unsupported API level " + api); 255 } 256 257 // Configure EGL for OpenGL ES 1.0 or 2.0, with a pbuffer 258 int[] attribList = { 259 EGL14.EGL_RED_SIZE, 8, 260 EGL14.EGL_GREEN_SIZE, 8, 261 EGL14.EGL_BLUE_SIZE, 8, 262 EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, 263 EGL14.EGL_RENDERABLE_TYPE, renderableType, 264 EGL14.EGL_NONE 265 }; 266 EGLConfig[] configs = new EGLConfig[1]; 267 int[] numConfigs = new int[1]; 268 if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 269 numConfigs, 0)) { 270 throw new RuntimeException("unable to find RGB888+pbuffer ES" + api + " EGL config"); 271 } 272 273 // Create context 274 int[] attrib_list = { 275 EGL14.EGL_CONTEXT_CLIENT_VERSION, api, 276 EGL14.EGL_NONE 277 }; 278 mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, 279 attrib_list, 0); 280 checkEglError("eglCreateContext"); 281 if (mEGLContext == null) { 282 throw new RuntimeException("null context"); 283 } 284 285 // Create a 1x1 pbuffer surface 286 int[] surfaceAttribs = { 287 EGL14.EGL_WIDTH, width, 288 EGL14.EGL_HEIGHT, height, 289 EGL14.EGL_NONE 290 }; 291 mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0); 292 checkEglError("eglCreatePbufferSurface"); 293 if (mEGLSurface == null) { 294 throw new RuntimeException("surface was null"); 295 } 296 297 // Make it current 298 if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 299 throw new RuntimeException("eglMakeCurrent failed"); 300 } 301 } 302 303 /** 304 * Releases EGL goodies. 305 */ 306 private void eglRelease() { 307 // Terminating the display will release most objects, but won't discard the current 308 // surfaces and context until we release the thread. It shouldn't matter what order 309 // we do these in. 310 if (mEGLDisplay != null) { 311 EGL14.eglTerminate(mEGLDisplay); 312 EGL14.eglReleaseThread(); 313 } 314 315 // null everything out so future attempts to use this object will cause an NPE 316 mEGLDisplay = null; 317 mEGLContext = null; 318 mEGLSurface = null; 319 } 320 321 /** 322 * Checks for EGL errors. 323 */ 324 private void checkEglError(String msg) { 325 boolean failed = false; 326 int error; 327 while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 328 Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error)); 329 failed = true; 330 } 331 if (failed) { 332 throw new RuntimeException("EGL error encountered (see log)"); 333 } 334 } 335 } 336