blob: 895a02b6daac8f85b111f5d8a07ccd7f9f667b59 [file] [log] [blame]
/*
* Copyright (C) 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.
*/
package android.opengl.cts;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES10;
import android.opengl.GLES20;
import android.test.AndroidTestCase;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
/**
* Test some aspects of the Java-language wrappers generated for OpenGL.
*/
public class WrapperTest extends AndroidTestCase {
private static final String TAG = "WrapperTest";
private EGLDisplay mEGLDisplay;
private EGLContext mEGLContext;
private EGLSurface mEGLSurface;
/**
* Tests range-checking on glGetIntegerv in GLES 1.x.
*/
public void testGetIntegerv1() {
eglSetup(1, 1, 1); // GLES 1.x with 1x1 pbuffer
checkGlError("start");
int[] countBuf = new int[1];
GLES10.glGetIntegerv(GLES10.GL_NUM_COMPRESSED_TEXTURE_FORMATS, countBuf, 0);
checkGlError("glGetIntegerv(count)");
int formatCount = countBuf[0];
Log.d(TAG, "got count=" + formatCount);
// try with a buffer large enough to hold all results
GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount], 0);
checkGlError("glGetIntegerv(full1)");
// try with an exact-fit IntBuffer
ByteBuffer fullByteBuf = ByteBuffer.allocateDirect(4 * formatCount);
IntBuffer fullIntBuf = fullByteBuf.asIntBuffer();
GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, fullIntBuf);
checkGlError("glGetIntegerv(full2)");
// try with an oversized IntBuffer with an offset
final int OFFSET = 5;
ByteBuffer oversizeByteBuf = ByteBuffer.allocateDirect(4 * (formatCount+OFFSET));
IntBuffer oversizeIntBuf = oversizeByteBuf.asIntBuffer();
oversizeIntBuf.position(OFFSET);
GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, oversizeIntBuf);
checkGlError("glGetIntegerv(full3)");
assertEquals(oversizeIntBuf.get(OFFSET), fullIntBuf.get(0));
// retry with a buffer that's too small -- should throw
ByteBuffer partialByteBuf = ByteBuffer.allocateDirect(4 * (formatCount - 1));
IntBuffer partialIntBuf = partialByteBuf.asIntBuffer();
try {
GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, partialIntBuf);
checkGlError("glGetIntegerv(partial1)");
throw new RuntimeException("buffer has overrun (intbuf)");
} catch (IllegalArgumentException iae) {
// good
}
try {
GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount-1], 0);
checkGlError("glGetIntegerv(partial2)");
throw new RuntimeException("buffer has overrun (int[])");
} catch (IllegalArgumentException iae) {
// good
}
eglRelease();
}
/**
* Tests range-checking on glGetIntegerv in GLES 2.x.
*/
public void testGetIntegerv2() {
eglSetup(2, 1, 1); // GLES 2.x with 1x1 pbuffer
checkGlError("start");
int[] countBuf = new int[1];
GLES20.glGetIntegerv(GLES20.GL_NUM_COMPRESSED_TEXTURE_FORMATS, countBuf, 0);
checkGlError("glGetIntegerv(count)");
int formatCount = countBuf[0];
Log.d(TAG, "got count=" + formatCount);
// try with a buffer large enough to hold all results
GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount], 0);
checkGlError("glGetIntegerv(full1)");
// try with an exact-fit IntBuffer
ByteBuffer fullByteBuf = ByteBuffer.allocateDirect(4 * formatCount);
IntBuffer fullIntBuf = fullByteBuf.asIntBuffer();
GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, fullIntBuf);
checkGlError("glGetIntegerv(full2)");
// try with an oversized IntBuffer with an offset
final int OFFSET = 5;
ByteBuffer oversizeByteBuf = ByteBuffer.allocateDirect(4 * (formatCount+OFFSET));
IntBuffer oversizeIntBuf = oversizeByteBuf.asIntBuffer();
oversizeIntBuf.position(OFFSET);
GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, oversizeIntBuf);
checkGlError("glGetIntegerv(full3)");
assertEquals(oversizeIntBuf.get(OFFSET), fullIntBuf.get(0));
// retry with a buffer that's too small -- should throw
ByteBuffer partialByteBuf = ByteBuffer.allocateDirect(4 * (formatCount - 1));
IntBuffer partialIntBuf = partialByteBuf.asIntBuffer();
try {
GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, partialIntBuf);
checkGlError("glGetIntegerv(partial1)");
throw new RuntimeException("buffer has overrun (intbuf)");
} catch (IllegalArgumentException iae) {
// good
}
try {
GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, new int[formatCount-1], 0);
checkGlError("glGetIntegerv(partial2)");
throw new RuntimeException("buffer has overrun (int[])");
} catch (IllegalArgumentException iae) {
// good
}
eglRelease();
}
/**
* Tests whether EGL is releasing resources when the thread exits. If
* it doesn't, we'll consume memory rapidly, and will fail or be
* killed within a couple hundred iterations.
* <p>
* It may be worthwhile to watch the memory growth with procrank or showmap
* while the test runs to detect smaller leaks.
*/
public void testThreadCleanup() throws Throwable {
class WrappedTest implements Runnable {
public Throwable mThrowable;
private static final int WIDTH = 1280;
private static final int HEIGHT = 720;
@Override
public void run() {
try {
eglSetup(2, WIDTH, HEIGHT);
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
eglRelease();
} catch (Throwable th) {
mThrowable = th;
}
}
}
WrappedTest wrappedTest = new WrappedTest();
// Android has "reference-counted" EGL initialization. We want our eglTerminate call
// to be the "last" termination, since that's the situation we're trying to test, but
// it's possible that some previous test failed to balance eglInitialize and
// eglTerminate. So we call eglTerminate several times in a desperate attempt to
// zero out the refcount.
//
// Before we can terminate we need to be sure that the display has been initialized
// at least once.
eglSetup(2, 1, 1);
for (int i = 0; i < 100; i++) {
EGL14.eglTerminate(mEGLDisplay);
}
for (int i = 0; i < 1000; i++) {
if ((i % 25) == 0) {
Log.d(TAG, "iteration " + i);
}
Thread th = new Thread(wrappedTest, "EGL thrash");
th.start();
th.join();
if (wrappedTest.mThrowable != null) {
throw wrappedTest.mThrowable;
}
}
}
/**
* Checks for GL errors.
*/
public 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);
}
}
/**
* Prepares EGL. Pass in the desired GLES API version (1 or 2).
* <p>
* Sets mEGLDisplay, mEGLContext, and mEGLSurface, and makes them current.
*/
private void eglSetup(int api, int width, int height) {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}
int renderableType;
switch (api) {
case 1:
renderableType = EGL14.EGL_OPENGL_ES_BIT;
break;
case 2:
renderableType = EGL14.EGL_OPENGL_ES2_BIT;
break;
default:
throw new RuntimeException("unsupported API level " + api);
}
// Configure EGL for OpenGL ES 1.0 or 2.0, with a pbuffer
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
numConfigs, 0)) {
throw new RuntimeException("unable to find RGB888+pbuffer ES" + api + " EGL config");
}
// Create context
int[] attrib_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, api,
EGL14.EGL_NONE
};
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
attrib_list, 0);
checkEglError("eglCreateContext");
if (mEGLContext == null) {
throw new RuntimeException("null context");
}
// Create a 1x1 pbuffer surface
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE
};
mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);
checkEglError("eglCreatePbufferSurface");
if (mEGLSurface == null) {
throw new RuntimeException("surface was null");
}
// Make it current
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
/**
* Releases EGL goodies.
*/
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();
}
// null everything out so future attempts to use this object will cause an NPE
mEGLDisplay = null;
mEGLContext = null;
mEGLSurface = null;
}
/**
* Checks for EGL errors.
*/
private void checkEglError(String msg) {
boolean failed = false;
int error;
while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error));
failed = true;
}
if (failed) {
throw new RuntimeException("EGL error encountered (see log)");
}
}
}