/*
* 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 com.android.photos.views;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLUtils;
import android.util.Log;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
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;
import javax.microedition.khronos.opengles.GL10;
/**
* A TextureView that supports blocking rendering for synchronous drawing
*/
public class BlockingGLTextureView extends TextureView
implements SurfaceTextureListener {
private RenderThread mRenderThread;
public BlockingGLTextureView(Context context) {
super(context);
setSurfaceTextureListener(this);
}
public void setRenderer(Renderer renderer) {
if (mRenderThread != null) {
throw new IllegalArgumentException("Renderer already set");
}
mRenderThread = new RenderThread(renderer);
}
public void render() {
mRenderThread.render();
}
public void destroy() {
if (mRenderThread != null) {
mRenderThread.finish();
mRenderThread = null;
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
int height) {
mRenderThread.setSurface(surface);
mRenderThread.setSize(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
int height) {
mRenderThread.setSize(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (mRenderThread != null) {
mRenderThread.setSurface(null);
}
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
@Override
protected void finalize() throws Throwable {
try {
destroy();
} catch (Throwable t) {
// Ignore
}
super.finalize();
}
/**
* An EGL helper class.
*/
private static class EglHelper {
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private static final int EGL_OPENGL_ES2_BIT = 4;
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;
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 static int[] getConfig() {
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
};
}
EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
}
/**
* Initialize EGL for a given configuration spec.
*/
public void start() {
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
if (!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
mEglConfig = chooseEglConfig();
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
mEglSurface = null;
}
/**
* Create an egl surface for the current SurfaceTexture surface. If a surface
* already exists, destroy it before creating the new surface.
*
* @return true if the surface was created successfully.
*/
public boolean createSurface(SurfaceTexture surface) {
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
if (surface != null) {
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
/**
* Create a GL object for the current EGL context.
*/
public GL10 createGL() {
return (GL10) mEglContext.getGL();
}
/**
* Display the current render surface.
* @return the EGL error code from eglSwapBuffers.
*/
public int swap() {
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
}
public void destroySurface() {
destroySurfaceImp();
}
private void destroySurfaceImp() {
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
mEglSurface = null;
}
}
public void finish() {
if (mEglContext != null) {
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
mEglContext = null;
}
if (mEglDisplay != null) {
mEgl.eglTerminate(mEglDisplay);
mEglDisplay = null;
}
}
private void throwEglException(String function) {
throwEglException(function, mEgl.eglGetError());
}
public static void throwEglException(String function, int error) {
String message = formatEglError(function, error);
throw new RuntimeException(message);
}
public static void logEglErrorAsWarning(String tag, String function, int error) {
Log.w(tag, formatEglError(function, error));
}
public static String formatEglError(String function, int error) {
return function + " failed: " + error;
}
}
private static class RenderThread extends Thread {
private static final int INVALID = -1;
private static final int RENDER = 1;
private static final int CHANGE_SURFACE = 2;
private static final int RESIZE_SURFACE = 3;
private static final int FINISH = 4;
private EglHelper mEglHelper = new EglHelper();
private Object mLock = new Object();
private int mExecMsgId = INVALID;
private SurfaceTexture mSurface;
private Renderer mRenderer;
private int mWidth, mHeight;
private boolean mFinished = false;
private GL10 mGL;
public RenderThread(Renderer renderer) {
super("RenderThread");
mRenderer = renderer;
start();
}
private void checkRenderer() {
if (mRenderer == null) {
throw new IllegalArgumentException("Renderer is null!");
}
}
private void checkSurface() {
if (mSurface == null) {
throw new IllegalArgumentException("surface is null!");
}
}
public void setSurface(SurfaceTexture surface) {
// If the surface is null we're being torn down, don't need a
// renderer then
if (surface != null) {
checkRenderer();
}
mSurface = surface;
exec(CHANGE_SURFACE);
}
public void setSize(int width, int height) {
checkRenderer();
checkSurface();
mWidth = width;
mHeight = height;
exec(RESIZE_SURFACE);
}
public void render() {
checkRenderer();
if (mSurface != null) {
exec(RENDER);
mSurface.updateTexImage();
}
}
public void finish() {
mSurface = null;
exec(FINISH);
try {
join();
} catch (InterruptedException e) {
// Ignore
}
}
private void exec(int msgid) {
synchronized (mLock) {
if (mExecMsgId != INVALID) {
throw new IllegalArgumentException(
"Message already set - multithreaded access?");
}
mExecMsgId = msgid;
mLock.notify();
try {
mLock.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
private void handleMessageLocked(int what) {
switch (what) {
case CHANGE_SURFACE:
if (mEglHelper.createSurface(mSurface)) {
mGL = mEglHelper.createGL();
mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
}
break;
case RESIZE_SURFACE:
mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
break;
case RENDER:
mRenderer.onDrawFrame(mGL);
mEglHelper.swap();
break;
case FINISH:
mEglHelper.destroySurface();
mEglHelper.finish();
mFinished = true;
break;
}
}
@Override
public void run() {
synchronized (mLock) {
mEglHelper.start();
while (!mFinished) {
while (mExecMsgId == INVALID) {
try {
mLock.wait();
} catch (InterruptedException e) {
// Ignore
}
}
handleMessageLocked(mExecMsgId);
mExecMsgId = INVALID;
mLock.notify();
}
mExecMsgId = FINISH;
}
}
}
}