blob: 52a0a8d6f072d317a4ef5c63a54214d3b848b1e1 [file] [log] [blame]
/*
* Copyright (C) 2016 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.view.cts;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.opengl.GLUtils;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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;
public class TextureViewCtsActivity extends Activity implements SurfaceTextureListener {
private final static long TIME_OUT_MS = 10000;
private final Object mLock = new Object();
private View mPreview;
private TextureView mTextureView;
private HandlerThread mGLThreadLooper;
private Handler mGLThread;
private CountDownLatch mEnterAnimationFence = new CountDownLatch(1);
private SurfaceTexture mSurface;
private int mSurfaceWidth;
private int mSurfaceHeight;
private int mSurfaceUpdatedCount;
private int mEglColorSpace = 0;
private boolean mIsEGLWideGamut = false;
private boolean mEGLExtensionUnsupported = false;
static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
static final int EGL_OPENGL_ES2_BIT = 4;
static final int EGL_GL_COLORSPACE_KHR = 0x309D;
static final int EGL_COLOR_COMPONENT_TYPE_EXT = 0x3339;
static final int EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT = 0x333B;
private EGL10 mEgl;
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mGLThreadLooper == null) {
mGLThreadLooper = new HandlerThread("GLThread");
mGLThreadLooper.start();
mGLThread = new Handler(mGLThreadLooper.getLooper());
}
View preview = new View(this);
preview.setBackgroundColor(Color.WHITE);
mPreview = preview;
mTextureView = new TextureView(this);
mTextureView.setSurfaceTextureListener(this);
FrameLayout content = new FrameLayout(this);
content.setBackgroundColor(Color.BLACK);
content.addView(mTextureView,
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
content.addView(mPreview,
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
setContentView(content);
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
runOnGLThread(this::doFinishGL);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
mEnterAnimationFence.countDown();
}
public void waitForEnterAnimationComplete() throws TimeoutException, InterruptedException {
if (!mEnterAnimationFence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
throw new TimeoutException();
}
}
public boolean setWideColorGamut() throws Throwable {
CountDownLatch fence = new CountDownLatch(1);
RunSignalAndCatch wrapper = new RunSignalAndCatch(() -> {
this.getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
}, fence);
runOnUiThread(wrapper);
if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
throw new TimeoutException();
}
if (wrapper.error != null) {
throw wrapper.error;
}
return this.getWindow().getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
}
public Bitmap getContents(Bitmap.Config config, ColorSpace colorSpace) throws Throwable {
CountDownLatch fence = new CountDownLatch(1);
final Bitmap bitmap = Bitmap.createBitmap(this.getWindow().getDecorView().getWidth(),
this.getWindow().getDecorView().getHeight(), config, true, colorSpace);
RunSignalAndCatch wrapper = new RunSignalAndCatch(() -> {
this.getTextureView().getBitmap(bitmap);
}, fence);
runOnUiThread(wrapper);
if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
throw new TimeoutException();
}
if (wrapper.error != null) {
throw wrapper.error;
}
return bitmap;
}
private class RunSignalAndCatch implements Runnable {
public Throwable error;
private Runnable mRunnable;
private CountDownLatch mFence;
RunSignalAndCatch(Runnable run, CountDownLatch fence) {
mRunnable = run;
mFence = fence;
}
@Override
public void run() {
try {
mRunnable.run();
} catch (Throwable t) {
error = t;
} finally {
mFence.countDown();
}
}
}
private void runOnGLThread(Runnable r) throws Throwable {
CountDownLatch fence = new CountDownLatch(1);
RunSignalAndCatch wrapper = new RunSignalAndCatch(r, fence);
mGLThread.post(wrapper);
if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
throw new TimeoutException();
}
if (wrapper.error != null) {
throw wrapper.error;
}
}
public TextureView getTextureView() {
return mTextureView;
}
public void waitForSurface() throws InterruptedException {
synchronized (mLock) {
while (mSurface == null) {
mLock.wait(TIME_OUT_MS);
}
}
}
public boolean initGLExtensionUnsupported() {
return mEGLExtensionUnsupported;
}
public void initGl() throws Throwable {
initGl(0, false);
}
public void initGl(int eglColorSpace, boolean useHalfFloat) throws Throwable {
if (mEglSurface != null) {
if (eglColorSpace != mEglColorSpace || useHalfFloat != mIsEGLWideGamut) {
throw new RuntimeException("Cannot change config after initialization");
}
return;
}
mEglColorSpace = eglColorSpace;
mIsEGLWideGamut = useHalfFloat;
mEGLExtensionUnsupported = false;
runOnGLThread(mDoInitGL);
}
public void drawColor(int color) throws Throwable {
drawColor(Color.red(color) / 255.0f,
Color.green(color) / 255.0f,
Color.blue(color) / 255.0f,
Color.alpha(color) / 255.0f);
}
public void drawColor(float red, float green, float blue, float alpha) throws Throwable {
runOnGLThread(() -> {
glClearColor(red, green, blue, alpha);
glClear(GL_COLOR_BUFFER_BIT);
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
throw new RuntimeException("Cannot swap buffers");
}
});
}
interface DrawFrame {
void drawFrame(int width, int height);
}
public void drawFrame(Matrix transform, DrawFrame callback) throws Throwable {
CountDownLatch fence = new CountDownLatch(1);
runOnUiThread(() -> {
mTextureView.setTransform(transform);
fence.countDown();
});
waitForEnterAnimationComplete();
waitForSurface();
initGl();
fence.await();
int surfaceUpdateCount = mSurfaceUpdatedCount;
runOnGLThread(() -> {
callback.drawFrame(mSurfaceWidth, mSurfaceHeight);
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
throw new RuntimeException("Cannot swap buffers");
}
});
waitForSurfaceUpdateCount(surfaceUpdateCount + 1);
}
private static final Matrix IDENTITY = new Matrix();
public void drawFrame(DrawFrame callback) throws Throwable {
drawFrame(IDENTITY, callback);
}
public int waitForSurfaceUpdateCount(int updateCount) throws InterruptedException {
synchronized (mLock) {
while (updateCount > mSurfaceUpdatedCount) {
mLock.wait(TIME_OUT_MS);
}
return mSurfaceUpdatedCount;
}
}
public void removeCover() {
mPreview.setVisibility(View.GONE);
}
private void doFinishGL() {
if (mEglSurface != null) {
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
mEglSurface = null;
}
if (mEglContext != null) {
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
mEglContext = null;
}
if (mEglDisplay != null) {
mEgl.eglTerminate(mEglDisplay);
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
synchronized (mLock) {
mSurface = surface;
mSurfaceWidth = width;
mSurfaceHeight = height;
mLock.notifyAll();
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
synchronized (mLock) {
mSurface = null;
mLock.notifyAll();
}
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
synchronized (mLock) {
mSurfaceUpdatedCount++;
mLock.notifyAll();
}
}
private Runnable mDoInitGL = new Runnable() {
@Override
public void run() {
mEgl = (EGL10) EGLContext.getEGL();
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed "
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
}
int[] version = new int[2];
if (!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
}
// check extensions but still attempt to run the test, if the test fails then we check
// mEGLExtensionUnsupported to determine if the failure was expected.
String extensions = mEgl.eglQueryString(mEglDisplay, EGL10.EGL_EXTENSIONS);
if (mEglColorSpace != 0) {
String eglColorSpaceString = null;
switch (mEglColorSpace) {
case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_EXT:
eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3";
break;
case TextureViewTest.EGL_GL_COLORSPACE_SRGB_KHR:
eglColorSpaceString = "EGL_KHR_gl_colorspace";
break;
case TextureViewTest.EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
eglColorSpaceString = "EGL_EXT_gl_colorspace_scrgb_linear";
break;
default:
throw new RuntimeException("Unknown eglColorSpace: " + mEglColorSpace);
}
if (!extensions.contains(eglColorSpaceString)) {
mEGLExtensionUnsupported = true;
}
}
if (mIsEGLWideGamut && !extensions.contains("EXT_pixel_format_float")) {
mEGLExtensionUnsupported = true;
}
mEglConfig = chooseEglConfig();
if (mEglConfig == null) {
throw new RuntimeException("eglConfig not initialized");
}
mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
mSurface, (mEglColorSpace == 0) ? null :
new int[] { EGL_GL_COLORSPACE_KHR, mEglColorSpace, EGL10.EGL_NONE });
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
throw new RuntimeException("createWindowSurface failed "
+ GLUtils.getEGLErrorString(error));
}
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new RuntimeException("eglMakeCurrent failed "
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
}
}
};
EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
}
private EGLConfig chooseEglConfig() {
int[] configsCount = new int[1];
EGLConfig[] configs = new EGLConfig[1];
int[] configSpec = getConfig();
if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
throw new IllegalArgumentException("eglChooseConfig failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
} else if (configsCount[0] > 0) {
return configs[0];
}
return null;
}
private int[] getConfig() {
if (mIsEGLWideGamut) {
return new int[]{
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
EGL10.EGL_RED_SIZE, 16,
EGL10.EGL_GREEN_SIZE, 16,
EGL10.EGL_BLUE_SIZE, 16,
EGL10.EGL_ALPHA_SIZE, 16,
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_STENCIL_SIZE, 0,
EGL10.EGL_NONE
};
} else {
return new int[]{
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 8,
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_STENCIL_SIZE, 0,
EGL10.EGL_NONE
};
}
}
}