package com.af.experiments.FxCameraApp.ogles;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLDebugHelper;
import android.opengl.GLSurfaceView;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.TextureView;
import com.af.experiments.FxCameraApp.Utils.LogWriter;
import javax.microedition.khronos.egl.*;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import java.io.Writer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import static android.opengl.GLSurfaceView.DEBUG_CHECK_GL_ERROR;
import static android.opengl.GLSurfaceView.DEBUG_LOG_GL_CALLS;
import static android.opengl.GLSurfaceView.RENDERMODE_CONTINUOUSLY;
import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY;
import static javax.microedition.khronos.egl.EGL10.*;
import static javax.microedition.khronos.egl.EGL11.EGL_CONTEXT_LOST;
public class GlTextureView extends TextureView implements TextureView.SurfaceTextureListener {
private final static String TAG = "GLTextureView";
private final static boolean LOG_ATTACH_DETACH = false;
private final static boolean LOG_THREADS = false;
private final static boolean LOG_PAUSE_RESUME = false;
private final static boolean LOG_SURFACE = false;
private final static boolean LOG_RENDERER = false;
private final static boolean LOG_RENDERER_DRAW_FRAME = false;
private final static boolean LOG_EGL = false;
private static int sGLESVersion;
private SurfaceTextureListener mSurfaceTextureListener;
public GlTextureView(final Context context) {
super(context);
init(context);
}
public GlTextureView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public GlTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(final Context context) {
if (sGLESVersion == 0) {
final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
sGLESVersion = am.getDeviceConfigurationInfo().reqGlEsVersion;
}
super.setSurfaceTextureListener(this);
}
@Override
protected void finalize() throws Throwable {
try {
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
@Override
public void setSurfaceTextureListener(final SurfaceTextureListener listener) {
mSurfaceTextureListener = listener;
}
public void setGLWrapper(final GLSurfaceView.GLWrapper glWrapper) {
mGLWrapper = glWrapper;
}
public void setDebugFlags(final int debugFlags) {
mDebugFlags = debugFlags;
}
public int getDebugFlags() {
return mDebugFlags;
}
public void setPreserveEGLContextOnPause(final boolean preserveOnPause) {
mPreserveEGLContextOnPause = preserveOnPause;
}
public boolean getPreserveEGLContextOnPause() {
return mPreserveEGLContextOnPause;
}
public void setRenderer(final GLSurfaceView.Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new DefaultConfigChooser(true, mEGLContextClientVersion);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
public void setEGLContextFactory(final GLSurfaceView.EGLContextFactory factory) {
checkRenderThreadState();
mEGLContextFactory = factory;
}
public void setEGLWindowSurfaceFactory(final GLSurfaceView.EGLWindowSurfaceFactory factory) {
checkRenderThreadState();
mEGLWindowSurfaceFactory = factory;
}
public void setEGLConfigChooser(final GLSurfaceView.EGLConfigChooser configChooser) {
checkRenderThreadState();
mEGLConfigChooser = configChooser;
}
public void setEGLConfigChooser(final boolean needDepth) {
setEGLConfigChooser(new DefaultConfigChooser(needDepth, mEGLContextClientVersion));
}
public void setEGLConfigChooser(final int redSize, final int greenSize, final int blueSize, final int alphaSize, final int depthSize, final int stencilSize) {
setEGLConfigChooser(new DefaultConfigChooser(redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize, mEGLContextClientVersion));
}
public void setEGLContextClientVersion(final int version) {
checkRenderThreadState();
mEGLContextClientVersion = version;
}
public void setRenderMode(final int renderMode) {
mGLThread.setRenderMode(renderMode);
}
public int getRenderMode() {
return mGLThread.getRenderMode();
}
public void requestRender() {
mGLThread.requestRender();
}
@Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
mGLThread.surfaceCreated();
mGLThread.onWindowResize(width, height);
if (mSurfaceTextureListener != null) {
mSurfaceTextureListener.onSurfaceTextureAvailable(surface, width, height);
}
}
@Override
public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {
try {
if (mSurfaceTextureListener != null) {
mSurfaceTextureListener.onSurfaceTextureDestroyed(surface);
}
} finally {
mGLThread.surfaceDestroyed();
}
return true;
}
@Override
public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
mGLThread.onWindowResize(width, height);
if (mSurfaceTextureListener != null) {
mSurfaceTextureListener.onSurfaceTextureSizeChanged(surface, width, height);
}
}
@Override
public void onSurfaceTextureUpdated(final SurfaceTexture surface) {
if (mSurfaceTextureListener != null) {
mSurfaceTextureListener.onSurfaceTextureUpdated(surface);
}
}
public void onPause() {
mGLThread.onPause();
}
public void onResume() {
mGLThread.onResume();
}
public void queueEvent(final Runnable r) {
mGLThread.queueEvent(r);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRenderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
@Override
protected void onDetachedFromWindow() {
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onDetachedFromWindow");
}
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
private static class EglHelper {
public EglHelper(final WeakReference<GlTextureView> glSurfaceViewWeakRef) {
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
}
public void start() {
if (LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (mEglDisplay == 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");
}
GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view == null) {
mEglConfig = null;
mEglContext = null;
} else {
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
if (mEglContext == null || mEglContext == EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
}
public boolean createSurface() {
if (LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* 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.
*/
GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay, mEglConfig, view.getSurfaceTexture());
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
final int error = mEgl.eglGetError();
if (error == 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.
* @return
*/
GL createGL() {
GL gl = mEglContext.getGL();
final GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
if (view.mGLWrapper != null) {
gl = view.mGLWrapper.wrap(gl);
}
if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) {
int configFlags = 0;
Writer log = null;
if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) {
configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
}
if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) {
log = new LogWriter(GlTextureView.TAG);
}
gl = GLDebugHelper.wrap(gl, configFlags, log);
}
}
return gl;
}
/**
* Display the current render surface.
* @return the EGL error code from eglSwapBuffers.
*/
public int swap() {
if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL_SUCCESS;
}
public void destroySurface() {
if (LOG_EGL) {
Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId());
}
destroySurfaceImp();
}
private void destroySurfaceImp() {
if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
EGL_NO_SURFACE,
EGL_NO_CONTEXT);
final GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
}
mEglSurface = null;
}
}
public void finish() {
if (LOG_EGL) {
Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId());
}
if (mEglContext != null) {
final GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
}
mEglContext = null;
}
if (mEglDisplay != null) {
mEgl.eglTerminate(mEglDisplay);
mEglDisplay = null;
}
}
private void throwEglException(final String function) {
throwEglException(function, mEgl.eglGetError());
}
public static void throwEglException(final String function, final int error) {
final String message = formatEglError(function, error);
if (LOG_THREADS) {
Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + message);
}
throw new RuntimeException(message);
}
public static void logEglErrorAsWarning(final String tag, final String function, final int error) {
Log.w(tag, formatEglError(function, error));
}
public static String formatEglError(final String function, final int error) {
return function + " failed: " + EGLLogWrapper.getErrorString(error);
}
private WeakReference<GlTextureView> mGLSurfaceViewWeakRef;
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;
}
/**
* A generic GL Thread. Takes care of initializing EGL and GL. Delegates
* to a Renderer instance to do the actual drawing. Can be configured to
* render continuously or on request.
*
* All potentially blocking synchronization is done through the
* sGLThreadManager object. This avoids multiple-lock ordering issues.
*
*/
static class GLThread extends Thread {
GLThread(final WeakReference<GlTextureView> glSurfaceViewWeakRef) {
super();
mWidth = 0;
mHeight = 0;
mRequestRender = true;
mRenderMode = RENDERMODE_CONTINUOUSLY;
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
}
@Override
public void run() {
setName("GLThread " + getId());
if (LOG_THREADS) {
Log.i("GLThread", "starting tid=" + getId());
}
try {
guardedRun();
} catch (final InterruptedException e) {
// fall thru and exit normally
} finally {
sGLThreadManager.threadExiting(this);
}
}
/*
* This private method should only be called inside a
* synchronized(sGLThreadManager) block.
*/
private void stopEglSurfaceLocked() {
if (mHaveEglSurface) {
mHaveEglSurface = false;
mEglHelper.destroySurface();
}
}
/*
* This private method should only be called inside a
* synchronized(sGLThreadManager) block.
*/
private void stopEglContextLocked() {
if (mHaveEglContext) {
mEglHelper.finish();
mHaveEglContext = false;
sGLThreadManager.releaseEglContextLocked(this);
}
}
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
mHaveEglContext = false;
mHaveEglSurface = false;
try {
GL10 gl = null;
boolean createEglContext = false;
boolean createEglSurface = false;
boolean createGlInterface = false;
boolean lostEglContext = false;
boolean sizeChanged = false;
boolean wantRenderNotification = false;
boolean doRenderNotification = false;
boolean askedToReleaseEglContext = false;
int w = 0;
int h = 0;
Runnable event = null;
while (true) {
synchronized (sGLThreadManager) {
while (true) {
if (mShouldExit) {
return;
}
if (! mEventQueue.isEmpty()) {
event = mEventQueue.remove(0);
break;
}
// Update the pause state.
boolean pausing = false;
if (mPaused != mRequestPaused) {
pausing = mRequestPaused;
mPaused = mRequestPaused;
sGLThreadManager.notifyAll();
if (LOG_PAUSE_RESUME) {
Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
}
}
// Do we need to give up the EGL context?
if (mShouldReleaseEglContext) {
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
}
stopEglSurfaceLocked();
stopEglContextLocked();
mShouldReleaseEglContext = false;
askedToReleaseEglContext = true;
}
// Have we lost the EGL context?
if (lostEglContext) {
stopEglSurfaceLocked();
stopEglContextLocked();
lostEglContext = false;
}
// When pausing, release the EGL surface:
if (pausing && mHaveEglSurface) {
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
}
stopEglSurfaceLocked();
}
// When pausing, optionally release the EGL Context:
if (pausing && mHaveEglContext) {
final GlTextureView view = mGLSurfaceViewWeakRef.get();
final boolean preserveEglContextOnPause = view == null ? false : view.mPreserveEGLContextOnPause;
if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
stopEglContextLocked();
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
}
}
}
// When pausing, optionally terminate EGL:
if (pausing) {
if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {
mEglHelper.finish();
if (LOG_SURFACE) {
Log.i("GLThread", "terminating EGL because paused tid=" + getId());
}
}
}
// Have we lost the SurfaceView surface?
if ((! mHasSurface) && (! mWaitingForSurface)) {
if (LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
}
if (mHaveEglSurface) {
stopEglSurfaceLocked();
}
mWaitingForSurface = true;
mSurfaceIsBad = false;
sGLThreadManager.notifyAll();
}
// Have we acquired the surface view surface?
if (mHasSurface && mWaitingForSurface) {
if (LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId());
}
mWaitingForSurface = false;
sGLThreadManager.notifyAll();
}
if (doRenderNotification) {
if (LOG_SURFACE) {
Log.i("GLThread", "sending render notification tid=" + getId());
}
wantRenderNotification = false;
doRenderNotification = false;
mRenderComplete = true;
sGLThreadManager.notifyAll();
}
// Ready to draw?
if (readyToDraw()) {
// If we don't have an EGL context, try to acquire one.
if (! mHaveEglContext) {
if (askedToReleaseEglContext) {
askedToReleaseEglContext = false;
} else if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
try {
mEglHelper.start();
} catch (final RuntimeException t) {
sGLThreadManager.releaseEglContextLocked(this);
throw t;
}
mHaveEglContext = true;
createEglContext = true;
sGLThreadManager.notifyAll();
}
}
if (mHaveEglContext && !mHaveEglSurface) {
mHaveEglSurface = true;
createEglSurface = true;
createGlInterface = true;
sizeChanged = true;
}
if (mHaveEglSurface) {
if (mSizeChanged) {
sizeChanged = true;
w = mWidth;
h = mHeight;
wantRenderNotification = true;
if (LOG_SURFACE) {
Log.i("GLThread", "noticing that we want render notification tid=" + getId());
}
// Destroy and recreate the EGL surface.
createEglSurface = true;
mSizeChanged = false;
}
mRequestRender = false;
sGLThreadManager.notifyAll();
break;
}
}
// By design, this is the only place in a GLThread thread where we wait().
if (LOG_THREADS) {
Log.i("GLThread", "waiting tid=" + getId()
+ " mHaveEglContext: " + mHaveEglContext
+ " mHaveEglSurface: " + mHaveEglSurface
+ " mPaused: " + mPaused
+ " mHasSurface: " + mHasSurface
+ " mSurfaceIsBad: " + mSurfaceIsBad
+ " mWaitingForSurface: " + mWaitingForSurface
+ " mWidth: " + mWidth
+ " mHeight: " + mHeight
+ " mRequestRender: " + mRequestRender
+ " mRenderMode: " + mRenderMode);
}
sGLThreadManager.wait();
}
} // end of synchronized(sGLThreadManager)
if (event != null) {
event.run();
event = null;
continue;
}
if (createEglSurface) {
if (LOG_SURFACE) {
Log.w("GLThread", "egl createSurface");
}
if (!mEglHelper.createSurface()) {
synchronized (sGLThreadManager) {
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
continue;
}
createEglSurface = false;
}
if (createGlInterface) {
gl = (GL10) mEglHelper.createGL();
sGLThreadManager.checkGLDriver(gl);
createGlInterface = false;
}
if (createEglContext) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceCreated");
}
final GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
}
createEglContext = false;
}
if (sizeChanged) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
}
final GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
view.mRenderer.onSurfaceChanged(gl, w, h);
}
sizeChanged = false;
}
if (LOG_RENDERER_DRAW_FRAME) {
Log.w("GLThread", "onDrawFrame tid=" + getId());
}
{
final GlTextureView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
view.mRenderer.onDrawFrame(gl);
}
}
final int swapError = mEglHelper.swap();
switch (swapError) {
case EGL_SUCCESS:
break;
case EGL_CONTEXT_LOST:
if (LOG_SURFACE) {
Log.i("GLThread", "egl context lost tid=" + getId());
}
lostEglContext = true;
break;
default:
// Other errors typically mean that the current surface is bad,
// probably because the SurfaceView surface has been destroyed,
// but we haven't been notified yet.
// Log the error to help developers understand why rendering stopped.
EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
synchronized (sGLThreadManager) {
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
break;
}
if (wantRenderNotification) {
doRenderNotification = true;
}
}
} finally {
/*
* clean-up everything...
*/
synchronized (sGLThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
}
public boolean ableToDraw() {
return mHaveEglContext && mHaveEglSurface && readyToDraw();
}
private boolean readyToDraw() {
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
public void setRenderMode(final int renderMode) {
if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) {
throw new IllegalArgumentException("renderMode");
}
synchronized(sGLThreadManager) {
mRenderMode = renderMode;
sGLThreadManager.notifyAll();
}
}
public int getRenderMode() {
synchronized(sGLThreadManager) {
return mRenderMode;
}
}
public void requestRender() {
synchronized(sGLThreadManager) {
mRequestRender = true;
sGLThreadManager.notifyAll();
}
}
public void surfaceCreated() {
synchronized(sGLThreadManager) {
if (LOG_THREADS) {
Log.i("GLThread", "surfaceCreated tid=" + getId());
}
mHasSurface = true;
sGLThreadManager.notifyAll();
while((mWaitingForSurface) && (!mExited)) {
try {
sGLThreadManager.wait();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void surfaceDestroyed() {
synchronized(sGLThreadManager) {
if (LOG_THREADS) {
Log.i("GLThread", "surfaceDestroyed tid=" + getId());
}
mHasSurface = false;
sGLThreadManager.notifyAll();
while((!mWaitingForSurface) && (!mExited)) {
try {
sGLThreadManager.wait();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void onPause() {
synchronized (sGLThreadManager) {
if (LOG_PAUSE_RESUME) {
Log.i("GLThread", "onPause tid=" + getId());
}
mRequestPaused = true;
sGLThreadManager.notifyAll();
while ((! mExited) && (! mPaused)) {
if (LOG_PAUSE_RESUME) {
Log.i("Main thread", "onPause waiting for mPaused.");
}
try {
sGLThreadManager.wait();
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onResume() {
synchronized (sGLThreadManager) {
if (LOG_PAUSE_RESUME) {
Log.i("GLThread", "onResume tid=" + getId());
}
mRequestPaused = false;
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
while ((! mExited) && mPaused && (!mRenderComplete)) {
if (LOG_PAUSE_RESUME) {
Log.i("Main thread", "onResume waiting for !mPaused.");
}
try {
sGLThreadManager.wait();
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onWindowResize(final int width, final int height) {
synchronized (sGLThreadManager) {
mWidth = width;
mHeight = height;
mSizeChanged = true;
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
// Wait for thread to react to resize and render a frame
while (! mExited && !mPaused && !mRenderComplete && ableToDraw()) {
if (LOG_SURFACE) {
Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId());
}
try {
sGLThreadManager.wait();
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestExitAndWait() {
// don't call this from GLThread thread or it is a guaranteed
// deadlock!
synchronized(sGLThreadManager) {
mShouldExit = true;
sGLThreadManager.notifyAll();
while (! mExited) {
try {
sGLThreadManager.wait();
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestReleaseEglContextLocked() {
mShouldReleaseEglContext = true;
sGLThreadManager.notifyAll();
}
/**
* Queue an "event" to be run on the GL rendering thread.
* @param r the runnable to be run on the GL rendering thread.
*/
public void queueEvent(final Runnable r) {
if (r == null) {
throw new IllegalArgumentException("r must not be null");
}
synchronized(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}
// Once the thread is started, all accesses to the following member
// variables are protected by the sGLThreadManager monitor
private boolean mShouldExit;
private boolean mExited;
private boolean mRequestPaused;
private boolean mPaused;
private boolean mHasSurface;
private boolean mSurfaceIsBad;
private boolean mWaitingForSurface;
private boolean mHaveEglContext;
private boolean mHaveEglSurface;
private boolean mShouldReleaseEglContext;
private int mWidth;
private int mHeight;
private int mRenderMode;
private boolean mRequestRender;
private boolean mRenderComplete;
private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
private boolean mSizeChanged = true;
// End of member variables protected by the sGLThreadManager monitor.
private EglHelper mEglHelper;
/**
* Set once at thread construction time, nulled out when the parent view is garbage
* called. This weak reference allows the GLSurfaceView to be garbage collected while
* the GLThread is still alive.
*/
private WeakReference<GlTextureView> mGLSurfaceViewWeakRef;
}
private void checkRenderThreadState() {
if (mGLThread != null) {
throw new IllegalStateException("setRenderer has already been called for this instance.");
}
}
private static class GLThreadManager {
private static String TAG = "GLThreadManager";
public synchronized void threadExiting(final GLThread thread) {
if (LOG_THREADS) {
Log.i("GLThread", "exiting tid=" + thread.getId());
}
thread.mExited = true;
if (mEglOwner == thread) {
mEglOwner = null;
}
notifyAll();
}
/*
* Tries once to acquire the right to use an EGL context. Does not
* block. Requires that we are already in the sGLThreadManager monitor
* when this is called.
* @return true if the right to use an EGL context was acquired.
*/
public boolean tryAcquireEglContextLocked(final GLThread thread) {
if (mEglOwner == thread || mEglOwner == null) {
mEglOwner = thread;
notifyAll();
return true;
}
checkGLESVersion();
if (mMultipleGLESContextsAllowed) {
return true;
}
// Notify the owning thread that it should release the context.
// TODO: implement a fairness policy. Currently
// if the owning thread is drawing continuously it will just
// reacquire the EGL context.
if (mEglOwner != null) {
mEglOwner.requestReleaseEglContextLocked();
}
return false;
}
/*
* Releases the EGL context. Requires that we are already in the
* sGLThreadManager monitor when this is called.
*/
public void releaseEglContextLocked(final GLThread thread) {
if (mEglOwner == thread) {
mEglOwner = null;
}
notifyAll();
}
public synchronized boolean shouldReleaseEGLContextWhenPausing() {
// Release the EGL context when pausing even if
// the hardware supports multiple EGL contexts.
// Otherwise the device could run out of EGL contexts.
return mLimitedGLESContexts;
}
public synchronized boolean shouldTerminateEGLWhenPausing() {
checkGLESVersion();
return !mMultipleGLESContextsAllowed;
}
public synchronized void checkGLDriver(final GL10 gl) {
if (!mGLESDriverCheckComplete) {
checkGLESVersion();
final String renderer = gl.glGetString(GL10.GL_RENDERER);
if (sGLESVersion < kGLES_20) {
mMultipleGLESContextsAllowed = !renderer.startsWith(kMSM7K_RENDERER_PREFIX);
notifyAll();
}
mLimitedGLESContexts = !mMultipleGLESContextsAllowed || (Integer.parseInt(Build.VERSION.SDK) < JELLY_BEAN && renderer.startsWith(kADRENO));
if (LOG_SURFACE) {
Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = " + mMultipleGLESContextsAllowed + " mLimitedGLESContexts = " + mLimitedGLESContexts);
}
mGLESDriverCheckComplete = true;
}
}
private void checkGLESVersion() {
if (!mGLESVersionCheckComplete) {
if (sGLESVersion >= kGLES_20) {
mMultipleGLESContextsAllowed = true;
}
if (LOG_SURFACE) {
Log.w(TAG, "checkGLESVersion mGLESVersion =" + " " + sGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed);
}
mGLESVersionCheckComplete = true;
}
}
/**
* This check was required for some pre-Android-3.0 hardware. Android 3.0 provides
* support for hardware-accelerated views, therefore multiple EGL contexts are
* supported on all Android 3.0+ EGL drivers.
*/
private boolean mGLESVersionCheckComplete;
private boolean mGLESDriverCheckComplete;
private boolean mMultipleGLESContextsAllowed;
private boolean mLimitedGLESContexts;
private static final int kGLES_20 = 0x20000;
private static final String kMSM7K_RENDERER_PREFIX = "Q3Dimension MSM7500 ";
private static final String kADRENO = "Adreno";
private static final int JELLY_BEAN = 16;
private GLThread mEglOwner;
}
private static final GLThreadManager sGLThreadManager = new GLThreadManager();
private final WeakReference<GlTextureView> mThisWeakRef = new WeakReference<GlTextureView>(this);
private GLThread mGLThread;
private GLSurfaceView.Renderer mRenderer;
private boolean mDetached;
private GLSurfaceView.EGLConfigChooser mEGLConfigChooser;
private GLSurfaceView.EGLContextFactory mEGLContextFactory;
private GLSurfaceView.EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLSurfaceView.GLWrapper mGLWrapper;
private int mDebugFlags;
private int mEGLContextClientVersion;
private boolean mPreserveEGLContextOnPause;
}