package org.andengine.extension.opengl;
import java.util.ArrayList;
import javax.microedition.khronos.opengles.GL10;
import org.andengine.extension.opengl.GLWallpaperService.GLEngine;
import android.opengl.GLSurfaceView.EGLConfigChooser;
import android.opengl.GLSurfaceView.Renderer;
import android.view.SurfaceHolder;
class GLThread extends Thread {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private final GLThreadManager sGLThreadManager = new GLThreadManager();
private GLThread mEGLOwnerThread;
private final EGLConfigChooser mEGLConfigChooser;
public SurfaceHolder mSurfaceHolder;
private boolean mSizeChanged = true;
// Once the thread is started, all accesses to the following member
// variables are protected by the sGLThreadManager monitor
public boolean mDone;
private boolean mPaused;
private boolean mHasSurface;
private boolean mWaitingForSurface;
private boolean mHaveEGL;
private int mWidth;
private int mHeight;
private int mRenderMode = GLEngine.RENDERMODE_CONTINUOUSLY;
private boolean mRequestRender = true;
private boolean mEventsWaiting;
// End of member variables protected by the sGLThreadManager monitor.
private final Renderer mRenderer;
private final ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
private EGLHelper mEGLHelper;
// ===========================================================
// Constructors
// ===========================================================
GLThread(final Renderer pRenderer, final EGLConfigChooser pEGLConfigChooser) {
this.mRenderer = pRenderer;
this.mEGLConfigChooser = pEGLConfigChooser;
}
// ===========================================================
// Getter & Setter
// ===========================================================
private boolean isDone() {
synchronized (this.sGLThreadManager) {
return this.mDone;
}
}
public int getRenderMode() {
synchronized (this.sGLThreadManager) {
return this.mRenderMode;
}
}
public void setRenderMode(final int pRenderMode) {
if (GLEngine.RENDERMODE_WHEN_DIRTY != pRenderMode && pRenderMode != GLEngine.RENDERMODE_CONTINUOUSLY) {
throw new IllegalArgumentException("Illegal pRenderMode: '" + pRenderMode + "'.");
}
synchronized (this.sGLThreadManager) {
this.mRenderMode = pRenderMode;
if (pRenderMode == GLEngine.RENDERMODE_CONTINUOUSLY) {
this.sGLThreadManager.notifyAll();
}
}
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public void run() {
this.setName("GLThread " + this.getId());
try {
this.guardedRun();
} catch (final InterruptedException e) {
/* Fall through and exit normally. */
} finally {
this.sGLThreadManager.threadExiting(this);
}
}
// ===========================================================
// Methods
// ===========================================================
/**
* This private method should only be called inside a synchronized(sGLThreadManager) block.
*/
private void stopEGLLocked() {
if (this.mHaveEGL) {
this.mHaveEGL = false;
this.mEGLHelper.destroySurface();
this.mEGLHelper.finish();
this.sGLThreadManager.releaseEglSurface(this);
}
}
private void guardedRun() throws InterruptedException {
this.mEGLHelper = new EGLHelper(this.mEGLConfigChooser);
try {
GL10 gl = null;
boolean tellRendererSurfaceCreated = true;
boolean tellRendererSurfaceChanged = true;
/* This is our main activity thread's loop, we go until asked to quit. */
while (!this.isDone()) {
/* Update the asynchronous state (window size). */
int w = 0;
int h = 0;
boolean changed = false;
boolean needStart = false;
boolean eventsWaiting = false;
synchronized (this.sGLThreadManager) {
while (true) {
/* Manage acquiring and releasing the SurfaceView surface and the EGL surface. */
if (this.mPaused) {
this.stopEGLLocked();
}
if (!this.mHasSurface) {
if (!this.mWaitingForSurface) {
this.stopEGLLocked();
this.mWaitingForSurface = true;
this.sGLThreadManager.notifyAll();
}
} else {
if (!this.mHaveEGL) {
if (this.sGLThreadManager.tryAcquireEglSurface(this)) {
this.mHaveEGL = true;
this.mEGLHelper.start();
this.mRequestRender = true;
needStart = true;
}
}
}
/* Check if we need to wait. If not, update any state that needs to be updated,
* copy any state thatneeds to be copied, and use "break" to exit the wait loop. */
if (this.mDone) {
return;
}
if (this.mEventsWaiting) {
eventsWaiting = true;
this.mEventsWaiting = false;
break;
}
if ((!this.mPaused) && this.mHasSurface && this.mHaveEGL && (this.mWidth > 0) && (this.mHeight > 0) && (this.mRequestRender || (this.mRenderMode == GLEngine.RENDERMODE_CONTINUOUSLY))) {
changed = this.mSizeChanged;
w = this.mWidth;
h = this.mHeight;
this.mSizeChanged = false;
this.mRequestRender = false;
if (this.mHasSurface && this.mWaitingForSurface) {
changed = true;
this.mWaitingForSurface = false;
this.sGLThreadManager.notifyAll();
}
break;
}
this.sGLThreadManager.wait();
}
}
/* Handle queued events. */
if (eventsWaiting) {
Runnable r;
while ((r = this.getEvent()) != null) {
r.run();
if (this.isDone()) {
return;
}
}
/* Go back and see if we need to wait to render. */
continue;
}
if (needStart) {
tellRendererSurfaceCreated = true;
changed = true;
}
if (changed) {
gl = (GL10) this.mEGLHelper.createSurface(this.mSurfaceHolder);
tellRendererSurfaceChanged = true;
}
if (tellRendererSurfaceCreated) {
this.mRenderer.onSurfaceCreated(gl, this.mEGLHelper.mEGLConfig);
tellRendererSurfaceCreated = false;
}
if (tellRendererSurfaceChanged) {
this.mRenderer.onSurfaceChanged(gl, w, h);
tellRendererSurfaceChanged = false;
}
if ((w > 0) && (h > 0)) {
/* draw a frame here */
this.mRenderer.onDrawFrame(gl);
/* Once we're done with GL, we need to call swapBuffers() to
* instruct the system to display the rendered frame. */
this.mEGLHelper.swapBuffers();
}
}
} finally {
/* Clean-up everything. */
synchronized (this.sGLThreadManager) {
this.stopEGLLocked();
}
}
}
public void requestRender() {
synchronized (this.sGLThreadManager) {
this.mRequestRender = true;
this.sGLThreadManager.notifyAll();
}
}
public void surfaceCreated(final SurfaceHolder holder) {
this.mSurfaceHolder = holder;
synchronized (this.sGLThreadManager) {
this.mHasSurface = true;
this.sGLThreadManager.notifyAll();
}
}
public void surfaceDestroyed() {
synchronized (this.sGLThreadManager) {
this.mHasSurface = false;
this.sGLThreadManager.notifyAll();
while (!this.mWaitingForSurface && this.isAlive() && !this.mDone) {
try {
this.sGLThreadManager.wait();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void onPause() {
synchronized (this.sGLThreadManager) {
this.mPaused = true;
this.sGLThreadManager.notifyAll();
}
}
public void onResume() {
synchronized (this.sGLThreadManager) {
this.mPaused = false;
this.mRequestRender = true;
this.sGLThreadManager.notifyAll();
}
}
public void onWindowResize(final int w, final int h) {
synchronized (this.sGLThreadManager) {
this.mWidth = w;
this.mHeight = h;
this.mSizeChanged = true;
this.sGLThreadManager.notifyAll();
}
}
public void requestExitAndWait() {
// don't call this from GLThread thread or it is a guaranteed
// deadlock!
synchronized (this.sGLThreadManager) {
this.mDone = true;
this.sGLThreadManager.notifyAll();
}
try {
this.join();
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
/**
* 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) {
synchronized (this) {
this.mEventQueue.add(r);
synchronized (this.sGLThreadManager) {
this.mEventsWaiting = true;
this.sGLThreadManager.notifyAll();
}
}
}
private Runnable getEvent() {
synchronized (this) {
if (this.mEventQueue.size() > 0) {
return this.mEventQueue.remove(0);
}
}
return null;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
private class GLThreadManager {
public synchronized void threadExiting(final GLThread thread) {
thread.mDone = true;
if (GLThread.this.mEGLOwnerThread == thread) {
GLThread.this.mEGLOwnerThread = null;
}
this.notifyAll();
}
/*
* Tries once to acquire the right to use an EGL surface. Does not
* block.
*
* @return true if the right to use an EGL surface was acquired.
*/
public synchronized boolean tryAcquireEglSurface(final GLThread thread) {
if (GLThread.this.mEGLOwnerThread == thread || GLThread.this.mEGLOwnerThread == null) {
GLThread.this.mEGLOwnerThread = thread;
this.notifyAll();
return true;
}
return false;
}
public synchronized void releaseEglSurface(final GLThread thread) {
if (GLThread.this.mEGLOwnerThread == thread) {
GLThread.this.mEGLOwnerThread = null;
}
this.notifyAll();
}
}
}