From 57d019e222abc0de3f8876e682617d42872230c6 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 3 Apr 2013 17:28:14 -0700 Subject: rework GLSurfaceView so it can use the choreographer GLSurfaceView will now automatically use the Choreographer to schedule render updates on VSYNC. This happens only in RENDERMODE_CONTINUOUSLY. GLSurfaceView was rewriten to use a Looper and a HandlerThread instead of a custom thread. The basic mode of operation is unchanged, however the implementation is largely different. Removed support for older GPUs which had a limited number of contexts. Bug: 8485730 Change-Id: I7b270f7ad7bb1259e858429054b1c8c9cc159dc6 --- opengl/java/android/opengl/GLSurfaceView.java | 1038 +++++++++---------------- 1 file changed, 378 insertions(+), 660 deletions(-) (limited to 'opengl') diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 5a2e261..daa5d13 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -18,7 +18,6 @@ package android.opengl; import java.io.Writer; import java.lang.ref.WeakReference; -import java.util.ArrayList; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; @@ -30,10 +29,13 @@ import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; import android.content.Context; -import android.content.pm.ConfigurationInfo; -import android.os.SystemProperties; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Trace; import android.util.AttributeSet; import android.util.Log; +import android.view.Choreographer; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -164,11 +166,26 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private final static String TAG = "GLSurfaceView"; 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 final static boolean TRACE_ENABLED = false; + + private final WeakReference mThisWeakRef = + new WeakReference(this); + private GLThread mGLThread; + private Renderer mRenderer; + private boolean mDetached; + private EGLConfigChooser mEGLConfigChooser; + private EGLContextFactory mEGLContextFactory; + private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; + private GLWrapper mGLWrapper; + private int mDebugFlags; + private int mEGLContextClientVersion; + private boolean mPreserveEGLContextOnPause; + private int mUserRenderMode; + /** * The renderer only renders * when the surface is created, or when {@link #requestRender} is called. @@ -241,13 +258,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback // underlying surface is created and destroyed SurfaceHolder holder = getHolder(); holder.addCallback(this); - // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment - // this statement if back-porting to 2.2 or older: - // holder.setFormat(PixelFormat.RGB_565); - // - // setType is not needed for SDK 2.0 or newer. Uncomment this - // statement if back-porting this code to older SDKs. - // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); + mUserRenderMode = RENDERMODE_CONTINUOUSLY; } /** @@ -346,15 +357,16 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback public void setRenderer(Renderer renderer) { checkRenderThreadState(); if (mEGLConfigChooser == null) { - mEGLConfigChooser = new SimpleEGLConfigChooser(true); + mEGLConfigChooser = new SimpleEGLConfigChooser(true, mEGLContextClientVersion); } if (mEGLContextFactory == null) { - mEGLContextFactory = new DefaultContextFactory(); + mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion); } if (mEGLWindowSurfaceFactory == null) { mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); } mRenderer = renderer; + mGLThread = new GLThread(mThisWeakRef); mGLThread.start(); } @@ -420,7 +432,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * @param needDepth */ public void setEGLConfigChooser(boolean needDepth) { - setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); + setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth, mEGLContextClientVersion)); } /** @@ -439,7 +451,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, - blueSize, alphaSize, depthSize, stencilSize)); + blueSize, alphaSize, depthSize, stencilSize, mEGLContextClientVersion)); } /** @@ -466,6 +478,13 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * If * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config. + * + * This method must be called before: + *
    + *
  • {@link #setEGLConfigChooser(boolean)} + *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} + *
+ * * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0 */ public void setEGLContextClientVersion(int version) { @@ -490,6 +509,14 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * @see #RENDERMODE_WHEN_DIRTY */ public void setRenderMode(int renderMode) { + switch (renderMode) { + case RENDERMODE_WHEN_DIRTY: + case RENDERMODE_CONTINUOUSLY: + break; + default: + throw new IllegalArgumentException("renderMode"); + } + mUserRenderMode = renderMode; mGLThread.setRenderMode(renderMode); } @@ -501,7 +528,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * @see #RENDERMODE_WHEN_DIRTY */ public int getRenderMode() { - return mGLThread.getRenderMode(); + return mUserRenderMode; } /** @@ -582,14 +609,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback 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.setRenderMode(mUserRenderMode); mGLThread.start(); } mDetached = false; @@ -761,11 +782,15 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); } - private class DefaultContextFactory implements EGLContextFactory { - private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private static class DefaultContextFactory implements EGLContextFactory { + private final int mEGLContextClientVersion; + + public DefaultContextFactory(int version) { + mEGLContextClientVersion = version; + } public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { - int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, + int[] attrib_list = {EGL14.EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, EGL10.EGL_NONE }; return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, @@ -775,9 +800,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { if (!egl.eglDestroyContext(display, context)) { - Log.e("DefaultContextFactory", "display:" + display + " context: " + context); + Log.e(TAG, "display:" + display + " context: " + context); if (LOG_THREADS) { - Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); + Log.d(TAG, "tid=" + Thread.currentThread().getId()); } EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); } @@ -807,8 +832,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback try { result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); } catch (IllegalArgumentException e) { - // This exception indicates that the surface flinger surface - // is not valid. This can happen if the surface flinger surface has + // This exception indicates that the surfaceflinger surface + // is not valid. This can happen if the surfaceflinger surface has // been torn down, but the application has not yet been // notified via SurfaceHolder.Callback.surfaceDestroyed. // In theory the application should be notified first, @@ -844,10 +869,11 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); } - private abstract class BaseConfigChooser + private static abstract class BaseConfigChooser implements EGLConfigChooser { - public BaseConfigChooser(int[] configSpec) { - mConfigSpec = filterConfigSpec(configSpec); + + public BaseConfigChooser(int[] configSpec, int version) { + mConfigSpec = filterConfigSpec(configSpec, version); } public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { @@ -881,8 +907,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback protected int[] mConfigSpec; - private int[] filterConfigSpec(int[] configSpec) { - if (mEGLContextClientVersion != 2) { + private int[] filterConfigSpec(int[] configSpec, int version) { + if (version != 2) { return configSpec; } /* We know none of the subclasses define EGL_RENDERABLE_TYPE. @@ -892,7 +918,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback int[] newConfigSpec = new int[len + 2]; System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; - newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ + newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT; newConfigSpec[len+1] = EGL10.EGL_NONE; return newConfigSpec; } @@ -902,9 +928,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * Choose a configuration with exactly the specified r,g,b,a sizes, * and at least the specified depth and stencil sizes. */ - private class ComponentSizeChooser extends BaseConfigChooser { + private static class ComponentSizeChooser extends BaseConfigChooser { public ComponentSizeChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize) { + int alphaSize, int depthSize, int stencilSize, int version) { super(new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, @@ -912,7 +938,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, EGL10.EGL_STENCIL_SIZE, stencilSize, - EGL10.EGL_NONE}); + EGL10.EGL_NONE}, version); mValue = new int[1]; mRedSize = redSize; mGreenSize = greenSize; @@ -920,7 +946,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mAlphaSize = alphaSize; mDepthSize = depthSize; mStencilSize = stencilSize; - } + } @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, @@ -931,14 +957,10 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); if ((d >= mDepthSize) && (s >= mStencilSize)) { - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); + int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); if ((r == mRedSize) && (g == mGreenSize) && (b == mBlueSize) && (a == mAlphaSize)) { return config; @@ -965,16 +987,16 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback protected int mAlphaSize; protected int mDepthSize; protected int mStencilSize; - } + } /** * This class will choose a RGB_888 surface with * or without a depth buffer. * */ - private class SimpleEGLConfigChooser extends ComponentSizeChooser { - public SimpleEGLConfigChooser(boolean withDepthBuffer) { - super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); + private static class SimpleEGLConfigChooser extends ComponentSizeChooser { + public SimpleEGLConfigChooser(boolean withDepthBuffer, int version) { + super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0, version); } } @@ -991,9 +1013,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * Initialize EGL for a given configuration spec. * @param configSpec */ - public void start() { + public void initialize() { if (LOG_EGL) { - Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); + Log.d(TAG, "initialize() tid=" + Thread.currentThread().getId()); } /* * Get an EGL instance @@ -1034,7 +1056,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback throwEglException("createContext"); } if (LOG_EGL) { - Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); + Log.d(TAG, "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); } mEglSurface = null; @@ -1048,7 +1070,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback */ public boolean createSurface() { if (LOG_EGL) { - Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); + Log.d(TAG, "createSurface() tid=" + Thread.currentThread().getId()); } /* * Check preconditions. @@ -1083,7 +1105,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback 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."); + Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); } return false; } @@ -1097,8 +1119,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */ - logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); - return false; + logEglErrorAsWarning(TAG, "eglMakeCurrent", mEgl.eglGetError()); + // we fall-through to "true" here because we do have a + // valid EGLSurface at this point. } return true; @@ -1108,8 +1131,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * Create a GL object for the current EGL context. * @return */ - GL createGL() { - + public GL createGL() { GL gl = mEglContext.getGL(); GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { @@ -1145,7 +1167,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback public void destroySurface() { if (LOG_EGL) { - Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); + Log.d(TAG, "destroySurface() tid=" + Thread.currentThread().getId()); } destroySurfaceImp(); } @@ -1163,9 +1185,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } - public void finish() { + public void terminate() { if (LOG_EGL) { - Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); + Log.d(TAG, "terminate() tid=" + Thread.currentThread().getId()); } if (mEglContext != null) { GLSurfaceView view = mGLSurfaceViewWeakRef.get(); @@ -1187,7 +1209,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback public static void throwEglException(String function, int error) { String message = formatEglError(function, error); if (LOG_THREADS) { - Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + Log.e(TAG, "throwEglException tid=" + Thread.currentThread().getId() + " " + message); } throw new RuntimeException(message); @@ -1207,584 +1229,411 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback 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(WeakReference glSurfaceViewWeakRef) { - super(); + + static class GLThread extends HandlerThread { + // only accessed from GLThread + private GL10 mGLContext; + private int mWidth; + private int mHeight; + private boolean mSizeChanged; + // current render mode + private int mRenderMode; + // the EGLSurface exists but isn't working for some reason + private boolean mEglSurfaceIsBad; + // we have an EGLContext + private boolean mHaveEglContext; + // we have an EGLSurface + private boolean mHaveEglSurface; + // we have a Surface (i.e.: EGLNativeWindowType) + private boolean mHasSurface; + // activity is paused + private boolean mPaused; + + // constants + private EglHelper mEglHelper; + private Handler mGLHandler; + private Choreographer mChoreographer; + + /* + * 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 final WeakReference mGLSurfaceViewWeakRef; + + private final Runnable mExecuteDrawAction = new Runnable() { + private int mTraceVsyncCounter = 0; + @Override + public void run() { + if (TRACE_ENABLED) { + Trace.traceCounter(Trace.TRACE_TAG_GRAPHICS, + "GLSurfaceView VSYNC counter", (mTraceVsyncCounter++) & 0xf); + } + executeDraw(); + } + }; + + public GLThread(WeakReference glSurfaceViewWeakRef) { + super("GLThread", android.os.Process.THREAD_PRIORITY_DISPLAY); + if (LOG_THREADS) { + Log.d(TAG, "*** Starting GLThread ***"); + } mWidth = 0; mHeight = 0; - mRequestRender = true; mRenderMode = RENDERMODE_CONTINUOUSLY; mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; } + private void readyToRun() { + mChoreographer = Choreographer.getInstance(); + mEglHelper = new EglHelper(mGLSurfaceViewWeakRef); + } + @Override - public void run() { - setName("GLThread " + getId()); - if (LOG_THREADS) { - Log.i("GLThread", "starting tid=" + getId()); - } + public void start() { + super.start(); + // getLooper() blocks until the thread is running + Looper looper = getLooper(); + mGLHandler = new Handler(looper); + // don't return until the GLThread state has been initialized + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + readyToRun(); + } + }, 0); + } + @Override + public void run() { try { - guardedRun(); - } catch (InterruptedException e) { - // fall thru and exit normally + super.run(); } finally { - sGLThreadManager.threadExiting(this); + // by definition the GLThread is not running anymore here + stopEglContext(); + stopEglSurface(); } } - /* - * This private method should only be called inside a - * synchronized(sGLThreadManager) block. - */ - private void stopEglSurfaceLocked() { + // only call from the GLThread + private void stopEglSurface() { if (mHaveEglSurface) { + if (LOG_SURFACE) { + Log.d(TAG, "releasing EGL surface because paused tid=" + getId()); + } mHaveEglSurface = false; mEglHelper.destroySurface(); } } - /* - * This private method should only be called inside a - * synchronized(sGLThreadManager) block. - */ - private void stopEglContextLocked() { + // only call from the GLThread + private void stopEglContext() { if (mHaveEglContext) { - mEglHelper.finish(); + mEglHelper.terminate(); mHaveEglContext = false; - sGLThreadManager.releaseEglContextLocked(this); + if (LOG_SURFACE) { + Log.d(TAG, "releasing EGL context because paused tid=" + getId()); + } } } - 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) { - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - 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(); - } + private void updateState() { + final boolean wasAbleToDraw = isAbleToDraw(); + if (!isReadyToDraw()) { + return; + } - // 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 (!mHaveEglSurface || mSizeChanged) { + // create EGL context if needed + boolean reportSurfaceCreated = false; + if (!mHaveEglContext) { + mEglHelper.initialize(); + mHaveEglContext = true; + reportSurfaceCreated = true; + } - if (doRenderNotification) { - if (LOG_SURFACE) { - Log.i("GLThread", "sending render notification tid=" + getId()); - } - wantRenderNotification = false; - doRenderNotification = false; - mRenderComplete = true; - sGLThreadManager.notifyAll(); - } + // get the GL interface for the active EGLContext + mGLContext = (GL10)mEglHelper.createGL(); - // 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 (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; - } - } + // create EGL Surface + mHaveEglSurface = mEglHelper.createSurface(); + mEglSurfaceIsBad = !mHaveEglSurface; + mSizeChanged = false; - // 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 - + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface - + " mPaused: " + mPaused - + " mHasSurface: " + mHasSurface - + " mSurfaceIsBad: " + mSurfaceIsBad - + " mWaitingForSurface: " + mWaitingForSurface - + " mWidth: " + mWidth - + " mHeight: " + mHeight - + " mRequestRender: " + mRequestRender - + " mRenderMode: " + mRenderMode); - } - sGLThreadManager.wait(); + // notify use of surface size change + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + if (reportSurfaceCreated) { + if (LOG_RENDERER) { + Log.d(TAG, "onSurfaceCreated"); } - } // end of synchronized(sGLThreadManager) - - if (event != null) { - event.run(); - event = null; - continue; + view.mRenderer.onSurfaceCreated(mGLContext, mEglHelper.mEglConfig); } - if (createEglSurface) { - if (LOG_SURFACE) { - Log.w("GLThread", "egl createSurface"); - } - if (mEglHelper.createSurface()) { - synchronized(sGLThreadManager) { - mFinishedCreatingEglSurface = true; - sGLThreadManager.notifyAll(); - } - } else { - synchronized(sGLThreadManager) { - mFinishedCreatingEglSurface = true; - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); - } - continue; - } - createEglSurface = false; + if (LOG_RENDERER) { + Log.d(TAG, "onSurfaceChanged(" + mWidth + ", " + mHeight + ")"); } + view.mRenderer.onSurfaceChanged(mGLContext, mWidth, mHeight); + } + } - if (createGlInterface) { - gl = (GL10) mEglHelper.createGL(); + // see if we should kick the rendering loop + if (!wasAbleToDraw && isAbleToDraw()) { + // we're now able to draw + if (mRenderMode == RENDERMODE_CONTINUOUSLY) { + requestRender(); + } + } - sGLThreadManager.checkGLDriver(gl); - createGlInterface = false; - } + // By design, this is the only place in a GLThread thread where we wait(). + if (LOG_THREADS) { + Log.d(TAG, "waiting tid=" + getId() + + " mHaveEglContext: " + mHaveEglContext + + " mHaveEglSurface: " + mHaveEglSurface + + " mPaused: " + mPaused + + " mHasSurface: " + mHasSurface + + " mSurfaceIsBad: " + mEglSurfaceIsBad + + " mWidth: " + mWidth + + " mHeight: " + mHeight + + " mRenderMode: " + mRenderMode); + } + } - if (createEglContext) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceCreated"); - } - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); - } - createEglContext = false; - } + private void executeDraw() { + if (TRACE_ENABLED) { + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "executeDraw"); + } - if (sizeChanged) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); - } - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onSurfaceChanged(gl, w, h); - } - sizeChanged = false; - } + if (isAbleToDraw()) { + if (mRenderMode == RENDERMODE_CONTINUOUSLY) { + requestRender(); + } - if (LOG_RENDERER_DRAW_FRAME) { - Log.w("GLThread", "onDrawFrame tid=" + getId()); - } - { - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onDrawFrame(gl); - } - } + if (LOG_RENDERER_DRAW_FRAME) { + Log.d(TAG, "onDrawFrame tid=" + getId()); + } + + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + view.mRenderer.onDrawFrame(mGLContext); int swapError = mEglHelper.swap(); switch (swapError) { case EGL10.EGL_SUCCESS: break; case EGL11.EGL_CONTEXT_LOST: if (LOG_SURFACE) { - Log.i("GLThread", "egl context lost tid=" + getId()); + Log.d(TAG, "egl context lost tid=" + getId()); } - lostEglContext = true; + stopEglSurface(); + stopEglContext(); 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(); - } + EglHelper.logEglErrorAsWarning(TAG, "eglSwapBuffers", swapError); + mEglSurfaceIsBad = true; break; } - - if (wantRenderNotification) { - doRenderNotification = true; - } } + } - } finally { - /* - * clean-up everything... - */ - synchronized (sGLThreadManager) { - stopEglSurfaceLocked(); - stopEglContextLocked(); - } + if (TRACE_ENABLED) { + Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } } - public boolean ableToDraw() { - return mHaveEglContext && mHaveEglSurface && readyToDraw(); + private boolean isAbleToDraw() { + return mHaveEglContext && mHaveEglSurface && isReadyToDraw(); } - private boolean readyToDraw() { - return (!mPaused) && mHasSurface && (!mSurfaceIsBad) - && (mWidth > 0) && (mHeight > 0) - && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); + private boolean isReadyToDraw() { + return (!mPaused) && mHasSurface && (!mEglSurfaceIsBad) + && (mWidth > 0) && (mHeight > 0); } - public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { - throw new IllegalArgumentException("renderMode"); + private boolean isEglContextReleasedWhenPausing() { + GLSurfaceView view = mGLSurfaceViewWeakRef.get(); + return (view != null) ? !view.mPreserveEGLContextOnPause : false; + } + + public void queueEvent(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("Runnable r must not be null"); } - synchronized(sGLThreadManager) { - mRenderMode = renderMode; - sGLThreadManager.notifyAll(); + mGLHandler.post(r); + } + + /* + * the call-backs below all run on the GLThread and implement state + * changes of the GLSurfaceView and Activity life cycle. + */ + + private void doSurfaceCreated() { + mHasSurface = true; + updateState(); + } + + private void doSurfaceDestroyed() { + if (mHasSurface) { + if (LOG_SURFACE) { + Log.d(TAG, "noticed surfaceView surface lost tid=" + getId()); + } + stopEglSurface(); } + mHasSurface = false; } - public int getRenderMode() { - synchronized(sGLThreadManager) { - return mRenderMode; + private void doPause() { + if (mPaused == false) { + mPaused = true; + stopEglSurface(); + // When pausing, optionally release the EGL Context: + if (mHaveEglContext && isEglContextReleasedWhenPausing()) { + stopEglContext(); + } } } - public void requestRender() { - synchronized(sGLThreadManager) { - mRequestRender = true; - sGLThreadManager.notifyAll(); + private void doResume() { + mPaused = false; + updateState(); + if (mRenderMode == RENDERMODE_WHEN_DIRTY) { + executeDraw(); } } + private void doWindowResize(final int width, final int height) { + // we were not drawing yet. Update the window size and + // state and attempt to draw a frame. + mSizeChanged = (mWidth != width || mHeight != height); + mWidth = width; + mHeight = height; + updateState(); + // we always (attempt to) draw a frame before returning + executeDraw(); + } + + private void doSetRenderMode(final int renderMode) { + mRenderMode = renderMode; + requestRender(); + } + + /* + * the call-backs below run on the main UI thread, they just + * wait while executing work on the GLThread. + */ + public void surfaceCreated() { - synchronized(sGLThreadManager) { - if (LOG_THREADS) { - Log.i("GLThread", "surfaceCreated tid=" + getId()); + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + doSurfaceCreated(); } - mHasSurface = true; - mFinishedCreatingEglSurface = false; - sGLThreadManager.notifyAll(); - while (mWaitingForSurface - && !mFinishedCreatingEglSurface - && !mExited) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } + }, 0); } public void surfaceDestroyed() { - synchronized(sGLThreadManager) { - if (LOG_THREADS) { - Log.i("GLThread", "surfaceDestroyed tid=" + getId()); + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + doSurfaceDestroyed(); } - mHasSurface = false; - sGLThreadManager.notifyAll(); - while((!mWaitingForSurface) && (!mExited)) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } + }, 0); } 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 (InterruptedException ex) { - Thread.currentThread().interrupt(); - } + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + doPause(); } - } + }, 0); } 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 (InterruptedException ex) { - Thread.currentThread().interrupt(); - } + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + doResume(); } - } + }, 0); } public void onWindowResize(int w, int h) { - synchronized (sGLThreadManager) { - mWidth = w; - mHeight = h; - 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 (InterruptedException ex) { - Thread.currentThread().interrupt(); - } + final int width = w; + final int height = h; + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + doWindowResize(width, height); } - } + }, 0); } - 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 (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } + /* + * the methods below can be called from any thread + */ + + public void requestRender() { + if (mRenderMode == RENDERMODE_CONTINUOUSLY) { + mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, + mExecuteDrawAction, null); + } else { + /* + * in RENDERMODE_WHEN_DIRTY we schedule the draw callback + * immediately because the developer is manager her + * timing loop manually -- in particular she could be + * using the Choreographer already. + */ + mGLHandler.post(mExecuteDrawAction); } } - public void requestReleaseEglContextLocked() { - mShouldReleaseEglContext = true; - sGLThreadManager.notifyAll(); + public void setRenderMode(final int renderMode) { + mGLHandler.runWithScissors(new Runnable() { + @Override + public void run() { + doSetRenderMode(renderMode); + } + }, 0); } - /** - * 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(Runnable r) { - if (r == null) { - throw new IllegalArgumentException("r must not be null"); - } - synchronized(sGLThreadManager) { - mEventQueue.add(r); - sGLThreadManager.notifyAll(); + public void requestExitAndWait() { + getLooper().quit(); + try { + this.join(); + } catch (InterruptedException e) { } } - - // 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 mFinishedCreatingEglSurface; - private boolean mShouldReleaseEglContext; - private int mWidth; - private int mHeight; - private int mRenderMode; - private boolean mRequestRender; - private boolean mRenderComplete; - private ArrayList mEventQueue = new ArrayList(); - 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 mGLSurfaceViewWeakRef; - - } + } // class GLThread static class LogWriter extends Writer { - - @Override public void close() { + @Override + public void close() { flushBuilder(); } - @Override public void flush() { + @Override + public void flush() { flushBuilder(); } - @Override public void write(char[] buf, int offset, int count) { - for(int i = 0; i < count; i++) { + @Override + public void write(char[] buf, int offset, int count) { + for (int i = 0; i < count; i++) { char c = buf[offset + i]; - if ( c == '\n') { + if (c == '\n') { flushBuilder(); - } - else { + } else { mBuilder.append(c); } } @@ -1792,7 +1641,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private void flushBuilder() { if (mBuilder.length() > 0) { - Log.v("GLSurfaceView", mBuilder.toString()); + Log.v(TAG, mBuilder.toString()); mBuilder.delete(0, mBuilder.length()); } } @@ -1800,141 +1649,10 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private StringBuilder mBuilder = new StringBuilder(); } - 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(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(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(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(GL10 gl) { - if (! mGLESDriverCheckComplete) { - checkGLESVersion(); - String renderer = gl.glGetString(GL10.GL_RENDERER); - if (mGLESVersion < kGLES_20) { - mMultipleGLESContextsAllowed = - ! renderer.startsWith(kMSM7K_RENDERER_PREFIX); - notifyAll(); - } - mLimitedGLESContexts = !mMultipleGLESContextsAllowed; - if (LOG_SURFACE) { - Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = " - + mMultipleGLESContextsAllowed - + " mLimitedGLESContexts = " + mLimitedGLESContexts); - } - mGLESDriverCheckComplete = true; - } - } - - private void checkGLESVersion() { - if (! mGLESVersionCheckComplete) { - mGLESVersion = SystemProperties.getInt( - "ro.opengles.version", - ConfigurationInfo.GL_ES_VERSION_UNDEFINED); - if (mGLESVersion >= kGLES_20) { - mMultipleGLESContextsAllowed = true; - } - if (LOG_SURFACE) { - Log.w(TAG, "checkGLESVersion mGLESVersion =" + - " " + mGLESVersion + " 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 int mGLESVersion; - 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 GLThread mEglOwner; - } - - private static final GLThreadManager sGLThreadManager = new GLThreadManager(); - - private final WeakReference mThisWeakRef = - new WeakReference(this); - private GLThread mGLThread; - private Renderer mRenderer; - private boolean mDetached; - private EGLConfigChooser mEGLConfigChooser; - private EGLContextFactory mEGLContextFactory; - private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; - private GLWrapper mGLWrapper; - private int mDebugFlags; - private int mEGLContextClientVersion; - private boolean mPreserveEGLContextOnPause; } -- cgit v1.1