From 1b4ecc63c4eceb7c125d4e749fd5f747d99d6ec6 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 13 Jul 2010 19:09:37 -0700 Subject: Fix deadlock when switching between two GLSurfaceViews Some devices only support a single active EGL context. On those devices, when a second activity that uses a GLSurfaceView is started in the same process, the second activity can potentially hang in GLSurfaceView.onWindowResize waiting for its GLSurfaceView render thread to draw a frame. The second activity's render thread is waiting to acquire an EGL context, but the first activity's render thread doesn't know it should release the EGL context. The fix is to detect the potential hang, and ask the first activity's render thread to release the EGL context. Change-Id: Ibb342c68772297744c973bcf5010581cd132db67 --- opengl/java/android/opengl/GLSurfaceView.java | 92 +++++++++++++++++++++------ 1 file changed, 74 insertions(+), 18 deletions(-) (limited to 'opengl') diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 2ff231d..41207f7 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -222,6 +222,10 @@ 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); @@ -1103,7 +1107,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mRenderer = renderer; } - @Override public void run() { setName("GLThread " + getId()); @@ -1154,6 +1157,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback boolean sizeChanged = false; boolean wantRenderNotification = false; boolean doRenderNotification = false; + boolean askedToReleaseEglContext = false; int w = 0; int h = 0; Runnable event = null; @@ -1179,6 +1183,17 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } + // 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(); @@ -1228,6 +1243,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } if (doRenderNotification) { + if (LOG_SURFACE) { + Log.i("GLThread", "sending render notification tid=" + getId()); + } wantRenderNotification = false; doRenderNotification = false; mRenderComplete = true; @@ -1235,22 +1253,24 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } // Ready to draw? - if ((!mPaused) && mHasSurface - && (mWidth > 0) && (mHeight > 0) - && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) { + if (readyToDraw()) { // If we don't have an EGL context, try to acquire one. - if ((! mHaveEglContext) && sGLThreadManager.tryAcquireEglContextLocked(this)) { - try { - mEglHelper.start(); - } catch (RuntimeException t) { - sGLThreadManager.releaseEglContextLocked(this); - throw t; - } - mHaveEglContext = true; - createEglContext = true; + 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(); + sGLThreadManager.notifyAll(); + } } if (mHaveEglContext && !mHaveEglSurface) { @@ -1265,6 +1285,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback w = mWidth; h = mHeight; wantRenderNotification = true; + if (LOG_SURFACE) { + Log.i("GLThread", "noticing that we want render notification tid=" + getId()); + } if (DRAW_TWICE_AFTER_SIZE_CHANGED) { // We keep mRequestRender true so that we draw twice after the size changes. @@ -1284,7 +1307,16 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback // By design, this is the only place in a GLThread thread where we wait(). if (LOG_THREADS) { - Log.i("GLThread", "waiting tid=" + getId()); + Log.i("GLThread", "waiting tid=" + getId() + + " mHaveEglContext: " + mHaveEglContext + + " mHaveEglSurface: " + mHaveEglSurface + + " mPaused: " + mPaused + + " mHasSurface: " + mHasSurface + + " mWaitingForSurface: " + mWaitingForSurface + + " mWidth: " + mWidth + + " mHeight: " + mHeight + + " mRequestRender: " + mRequestRender + + " mRenderMode: " + mRenderMode); } sGLThreadManager.wait(); } @@ -1326,7 +1358,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } if (LOG_RENDERER_DRAW_FRAME) { - Log.w("GLThread", "onDrawFrame"); + Log.w("GLThread", "onDrawFrame tid=" + getId()); } mRenderer.onDrawFrame(gl); if (!mEglHelper.swap()) { @@ -1352,6 +1384,16 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } + public boolean ableToDraw() { + return mHaveEglContext && mHaveEglSurface && readyToDraw(); + } + + private boolean readyToDraw() { + return (!mPaused) && mHasSurface + && (mWidth > 0) && (mHeight > 0) + && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); + } + public void setRenderMode(int renderMode) { if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { throw new IllegalArgumentException("renderMode"); @@ -1461,9 +1503,10 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback sGLThreadManager.notifyAll(); // Wait for thread to react to resize and render a frame - while (! mExited && !mPaused && !mRenderComplete ) { + while (! mExited && !mPaused && !mRenderComplete + && (mGLThread != null && mGLThread.ableToDraw())) { if (LOG_SURFACE) { - Log.i("Main thread", "onWindowResize waiting for render complete."); + Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + mGLThread.getId()); } try { sGLThreadManager.wait(); @@ -1490,6 +1533,11 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } + 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. @@ -1514,6 +1562,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private boolean mWaitingForSurface; private boolean mHaveEglContext; private boolean mHaveEglSurface; + private boolean mShouldReleaseEglContext; private int mWidth; private int mHeight; private int mRenderMode; @@ -1598,6 +1647,13 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback 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; } -- cgit v1.1