662 lines
20 KiB
Java
662 lines
20 KiB
Java
/*
|
|
* Copyright (C) 2008 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 android.opengl;
|
|
|
|
import java.io.IOException;
|
|
import java.io.Writer;
|
|
import java.util.ArrayList;
|
|
import java.util.concurrent.Semaphore;
|
|
|
|
import javax.microedition.khronos.egl.EGL10;
|
|
import javax.microedition.khronos.egl.EGL11;
|
|
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.GL;
|
|
import javax.microedition.khronos.opengles.GL10;
|
|
|
|
import android.content.Context;
|
|
import android.util.AttributeSet;
|
|
import android.util.Log;
|
|
import android.view.SurfaceHolder;
|
|
import android.view.SurfaceView;
|
|
|
|
/**
|
|
* An implementation of SurfaceView that uses the dedicated surface for
|
|
* displaying an OpenGL animation. This allows the animation to run in a
|
|
* separate thread, without requiring that it be driven by the update mechanism
|
|
* of the view hierarchy.
|
|
*
|
|
* The application-specific rendering code is delegated to a GLView.Renderer
|
|
* instance.
|
|
*/
|
|
public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
|
|
public final static int RENDERMODE_WHEN_DIRTY = 0;
|
|
public final static int RENDERMODE_CONTUOUSLY = 1;
|
|
|
|
/**
|
|
* Check glError() after every GL call.
|
|
*/
|
|
public final static int DEBUG_CHECK_GL_ERROR = 1;
|
|
|
|
/**
|
|
* Log GL calls to the system log at "verbose" level with tag "GLSurfaceView".
|
|
*/
|
|
public final static int DEBUG_LOG_GL_CALLS = 2;
|
|
|
|
public GLSurfaceView(Context context) {
|
|
super(context);
|
|
init();
|
|
}
|
|
|
|
public GLSurfaceView(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
init();
|
|
}
|
|
|
|
private void init() {
|
|
// Install a SurfaceHolder.Callback so we get notified when the
|
|
// underlying surface is created and destroyed
|
|
SurfaceHolder holder = getHolder();
|
|
holder.addCallback(this);
|
|
holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
|
|
}
|
|
|
|
/**
|
|
* Set the glWrapper to a new value. The current glWrapper is used
|
|
* whenever a surface is created. The default value is null.
|
|
* @param glWrapper the new GLWrapper
|
|
*/
|
|
public void setGLWrapper(GLWrapper glWrapper) {
|
|
mGLWrapper = glWrapper;
|
|
}
|
|
|
|
/**
|
|
* Set the debug flags to a new value. The debug flags take effect
|
|
* whenever a surface is created. The default value is zero.
|
|
* @param debugFlags the new debug flags
|
|
* @see #DEBUG_CHECK_GL_ERROR
|
|
* @see #DEBUG_LOG_GL_CALLS
|
|
*/
|
|
public void setDebugFlags(int debugFlags) {
|
|
mDebugFlags = debugFlags;
|
|
}
|
|
|
|
public int getDebugFlags() {
|
|
return mDebugFlags;
|
|
}
|
|
|
|
/**
|
|
* Set the renderer associated with this view. Can only be called once.
|
|
* @param renderer
|
|
*/
|
|
public void setRenderer(Renderer renderer) {
|
|
if (mGLThread != null) {
|
|
throw new IllegalStateException("setRenderer has already been called for this instance.");
|
|
}
|
|
mGLThread = new GLThread(renderer);
|
|
mGLThread.start();
|
|
}
|
|
|
|
/**
|
|
* Set the rendering mode. When the renderMode is
|
|
* RENDERMODE_CONTINUOUSLY, the renderer is called
|
|
* repeatedly to re-render the scene. When the rendermode
|
|
* is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
|
|
* is created, or when requestRender is called. Defaults to RENDERMODE_CONTINUOUSLY.
|
|
* @param renderMode one of the RENDERMODE_X constants
|
|
*/
|
|
public void setRenderMode(int renderMode) {
|
|
mGLThread.setRenderMode(renderMode);
|
|
}
|
|
|
|
/**
|
|
* Get the current rendering mode. May be called
|
|
* from any thread. Must not be called before a renderer has been set.
|
|
* @return true if the renderer will render continuously.
|
|
*/
|
|
public int getRenderMode() {
|
|
return mGLThread.getRenderMode();
|
|
}
|
|
|
|
/**
|
|
* Request that the renderer render a frame. May be called
|
|
* from any thread. Must not be called before a renderer has been set.
|
|
* This method is typically used when the render mode has been set to
|
|
* false, so that frames are only rendered on demand.
|
|
*/
|
|
public void requestRender() {
|
|
mGLThread.requestRender();
|
|
}
|
|
|
|
public void surfaceCreated(SurfaceHolder holder) {
|
|
mGLThread.surfaceCreated();
|
|
}
|
|
|
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
// Surface will be destroyed when we return
|
|
mGLThread.surfaceDestroyed();
|
|
}
|
|
|
|
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
|
|
mGLThread.onWindowResize(w, h);
|
|
}
|
|
|
|
/**
|
|
* Inform the view that the activity is paused. The owner of this view must
|
|
* call this method when the activity is paused.
|
|
*/
|
|
public void onPause() {
|
|
mGLThread.onPause();
|
|
}
|
|
|
|
/**
|
|
* Inform the view that the activity is resumed. The owner of this view must
|
|
* call this method when the activity is resumed.
|
|
*/
|
|
public void onResume() {
|
|
mGLThread.onResume();
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
mGLThread.queueEvent(r);
|
|
}
|
|
|
|
@Override
|
|
protected void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
mGLThread.requestExitAndWait();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
public interface GLWrapper {
|
|
GL wrap(GL gl);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
/**
|
|
* A generic renderer interface.
|
|
*/
|
|
public interface Renderer {
|
|
/**
|
|
* @return the EGL configuration specification desired by the renderer.
|
|
*/
|
|
int[] getConfigSpec();
|
|
|
|
/**
|
|
* Surface created.
|
|
* Called when the surface is created. Called when the application
|
|
* starts, and whenever the GPU is reinitialized. This will
|
|
* typically happen when the device awakes after going to sleep.
|
|
* Set your textures here.
|
|
* @param gl the GL interface. Use <code>instanceof</code> to
|
|
* test if the interface supports GL11 or higher interfaces.
|
|
* @param config the EGLConfig of the created surface. Can be used
|
|
* to create matching pbuffers.
|
|
*/
|
|
void onSurfaceCreated(GL10 gl, EGLConfig config);
|
|
/**
|
|
* Surface changed size.
|
|
* Called after the surface is created and whenever
|
|
* the OpenGL ES surface size changes. Set your viewport here.
|
|
* @param gl the GL interface. Use <code>instanceof</code> to
|
|
* test if the interface supports GL11 or higher interfaces.
|
|
* @param width
|
|
* @param height
|
|
*/
|
|
void onSurfaceChanged(GL10 gl, int width, int height);
|
|
/**
|
|
* Draw the current frame.
|
|
* @param gl the GL interface. Use <code>instanceof</code> to
|
|
* test if the interface supports GL11 or higher interfaces.
|
|
*/
|
|
void onDrawFrame(GL10 gl);
|
|
}
|
|
|
|
/**
|
|
* An EGL helper class.
|
|
*/
|
|
|
|
private class EglHelper {
|
|
public EglHelper() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Initialize EGL for a given configuration spec.
|
|
* @param configSpec
|
|
*/
|
|
public void start(int[] configSpec){
|
|
/*
|
|
* Get an EGL instance
|
|
*/
|
|
mEgl = (EGL10) EGLContext.getEGL();
|
|
|
|
/*
|
|
* Get to the default display.
|
|
*/
|
|
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
|
|
|
/*
|
|
* We can now initialize EGL for that display
|
|
*/
|
|
int[] version = new int[2];
|
|
mEgl.eglInitialize(mEglDisplay, version);
|
|
|
|
EGLConfig[] configs = new EGLConfig[1];
|
|
int[] num_config = new int[1];
|
|
mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1,
|
|
num_config);
|
|
mEglConfig = configs[0];
|
|
|
|
/*
|
|
* Create an OpenGL ES context. This must be done only once, an
|
|
* OpenGL context is a somewhat heavy object.
|
|
*/
|
|
mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,
|
|
EGL10.EGL_NO_CONTEXT, null);
|
|
|
|
mEglSurface = null;
|
|
}
|
|
|
|
/*
|
|
* React to the creation of a new surface by creating and returning an
|
|
* OpenGL interface that renders to that surface.
|
|
*/
|
|
public GL createSurface(SurfaceHolder holder) {
|
|
/*
|
|
* The window size has changed, so we need to create a new
|
|
* surface.
|
|
*/
|
|
if (mEglSurface != null) {
|
|
|
|
/*
|
|
* Unbind and destroy the old EGL surface, if
|
|
* there is one.
|
|
*/
|
|
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
|
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
|
|
}
|
|
|
|
/*
|
|
* Create an EGL surface we can render into.
|
|
*/
|
|
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay,
|
|
mEglConfig, holder, null);
|
|
|
|
/*
|
|
* Before we can issue GL commands, we need to make sure
|
|
* the context is current and bound to a surface.
|
|
*/
|
|
mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
|
|
mEglContext);
|
|
|
|
|
|
GL gl = mEglContext.getGL();
|
|
if (mGLWrapper != null) {
|
|
gl = mGLWrapper.wrap(gl);
|
|
}
|
|
|
|
if ((mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS))!= 0) {
|
|
int configFlags = 0;
|
|
Writer log = null;
|
|
if ((mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) {
|
|
configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
|
|
}
|
|
if ((mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) {
|
|
log = new LogWriter();
|
|
}
|
|
gl = GLDebugHelper.wrap(gl, configFlags, log);
|
|
}
|
|
return gl;
|
|
}
|
|
|
|
/**
|
|
* Display the current render surface.
|
|
* @return false if the context has been lost.
|
|
*/
|
|
public boolean swap() {
|
|
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
|
|
|
|
/*
|
|
* Always check for EGL_CONTEXT_LOST, which means the context
|
|
* and all associated data were lost (For instance because
|
|
* the device went to sleep). We need to sleep until we
|
|
* get a new surface.
|
|
*/
|
|
return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST;
|
|
}
|
|
|
|
public void finish() {
|
|
if (mEglSurface != null) {
|
|
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_CONTEXT);
|
|
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
|
|
mEglSurface = null;
|
|
}
|
|
if (mEglContext != null) {
|
|
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
|
|
mEglContext = null;
|
|
}
|
|
if (mEglDisplay != null) {
|
|
mEgl.eglTerminate(mEglDisplay);
|
|
mEglDisplay = null;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*
|
|
*/
|
|
class GLThread extends Thread {
|
|
GLThread(Renderer renderer) {
|
|
super();
|
|
mDone = false;
|
|
mWidth = 0;
|
|
mHeight = 0;
|
|
mRequestRender = true;
|
|
mRenderMode = RENDERMODE_CONTUOUSLY;
|
|
mRenderer = renderer;
|
|
setName("GLThread");
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
/*
|
|
* When the android framework launches a second instance of
|
|
* an activity, the new instance's onCreate() method may be
|
|
* called before the first instance returns from onDestroy().
|
|
*
|
|
* This semaphore ensures that only one instance at a time
|
|
* accesses EGL.
|
|
*/
|
|
try {
|
|
try {
|
|
sEglSemaphore.acquire();
|
|
} catch (InterruptedException e) {
|
|
return;
|
|
}
|
|
guardedRun();
|
|
} catch (InterruptedException e) {
|
|
// fall thru and exit normally
|
|
} finally {
|
|
sEglSemaphore.release();
|
|
}
|
|
}
|
|
|
|
private void guardedRun() throws InterruptedException {
|
|
mEglHelper = new EglHelper();
|
|
/*
|
|
* Specify a configuration for our opengl session
|
|
* and grab the first configuration that matches is
|
|
*/
|
|
int[] configSpec = mRenderer.getConfigSpec();
|
|
mEglHelper.start(configSpec);
|
|
|
|
GL10 gl = null;
|
|
boolean tellRendererSurfaceCreated = true;
|
|
boolean tellRendererSurfaceChanged = true;
|
|
|
|
/*
|
|
* This is our main activity thread's loop, we go until
|
|
* asked to quit.
|
|
*/
|
|
while (!mDone) {
|
|
|
|
/*
|
|
* Update the asynchronous state (window size)
|
|
*/
|
|
int w, h;
|
|
boolean changed;
|
|
boolean needStart = false;
|
|
synchronized (this) {
|
|
Runnable r;
|
|
while ((r = getEvent()) != null) {
|
|
r.run();
|
|
}
|
|
if (mPaused) {
|
|
mEglHelper.finish();
|
|
needStart = true;
|
|
}
|
|
while (needToWait()) {
|
|
wait();
|
|
}
|
|
if (mDone) {
|
|
break;
|
|
}
|
|
changed = mSizeChanged;
|
|
w = mWidth;
|
|
h = mHeight;
|
|
mSizeChanged = false;
|
|
mRequestRender = false;
|
|
}
|
|
if (needStart) {
|
|
mEglHelper.start(configSpec);
|
|
tellRendererSurfaceCreated = true;
|
|
changed = true;
|
|
}
|
|
if (changed) {
|
|
gl = (GL10) mEglHelper.createSurface(getHolder());
|
|
tellRendererSurfaceChanged = true;
|
|
}
|
|
if (tellRendererSurfaceCreated) {
|
|
mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
|
|
tellRendererSurfaceCreated = false;
|
|
}
|
|
if (tellRendererSurfaceChanged) {
|
|
mRenderer.onSurfaceChanged(gl, w, h);
|
|
tellRendererSurfaceChanged = false;
|
|
}
|
|
if ((w > 0) && (h > 0)) {
|
|
/* draw a frame here */
|
|
mRenderer.onDrawFrame(gl);
|
|
|
|
/*
|
|
* Once we're done with GL, we need to call swapBuffers()
|
|
* to instruct the system to display the rendered frame
|
|
*/
|
|
mEglHelper.swap();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* clean-up everything...
|
|
*/
|
|
mEglHelper.finish();
|
|
}
|
|
|
|
private boolean needToWait() {
|
|
if (mDone) {
|
|
return false;
|
|
}
|
|
|
|
if (mPaused || (! mHasSurface)) {
|
|
return true;
|
|
}
|
|
|
|
if ((mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTUOUSLY))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void setRenderMode(int renderMode) {
|
|
if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTUOUSLY)) ) {
|
|
throw new IllegalArgumentException("renderMode");
|
|
}
|
|
synchronized(this) {
|
|
mRenderMode = renderMode;
|
|
if (renderMode == RENDERMODE_CONTUOUSLY) {
|
|
notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int getRenderMode() {
|
|
synchronized(this) {
|
|
return mRenderMode;
|
|
}
|
|
}
|
|
|
|
public void requestRender() {
|
|
synchronized(this) {
|
|
mRequestRender = true;
|
|
notify();
|
|
}
|
|
}
|
|
|
|
public void surfaceCreated() {
|
|
synchronized(this) {
|
|
mHasSurface = true;
|
|
notify();
|
|
}
|
|
}
|
|
|
|
public void surfaceDestroyed() {
|
|
synchronized(this) {
|
|
mHasSurface = false;
|
|
notify();
|
|
}
|
|
}
|
|
|
|
public void onPause() {
|
|
synchronized (this) {
|
|
mPaused = true;
|
|
}
|
|
}
|
|
|
|
public void onResume() {
|
|
synchronized (this) {
|
|
mPaused = false;
|
|
notify();
|
|
}
|
|
}
|
|
|
|
public void onWindowResize(int w, int h) {
|
|
synchronized (this) {
|
|
mWidth = w;
|
|
mHeight = h;
|
|
mSizeChanged = true;
|
|
notify();
|
|
}
|
|
}
|
|
|
|
public void requestExitAndWait() {
|
|
// don't call this from GLThread thread or it is a guaranteed
|
|
// deadlock!
|
|
synchronized(this) {
|
|
mDone = true;
|
|
notify();
|
|
}
|
|
try {
|
|
join();
|
|
} catch (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(Runnable r) {
|
|
synchronized(this) {
|
|
mEventQueue.add(r);
|
|
}
|
|
}
|
|
|
|
private Runnable getEvent() {
|
|
synchronized(this) {
|
|
if (mEventQueue.size() > 0) {
|
|
return mEventQueue.remove(0);
|
|
}
|
|
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private boolean mDone;
|
|
private boolean mPaused;
|
|
private boolean mHasSurface;
|
|
private int mWidth;
|
|
private int mHeight;
|
|
private int mRenderMode;
|
|
private boolean mRequestRender;
|
|
private Renderer mRenderer;
|
|
private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
|
|
private EglHelper mEglHelper;
|
|
}
|
|
|
|
static class LogWriter extends Writer {
|
|
|
|
@Override public void close() {
|
|
flushBuilder();
|
|
}
|
|
|
|
@Override public void flush() {
|
|
flushBuilder();
|
|
}
|
|
|
|
@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') {
|
|
flushBuilder();
|
|
}
|
|
else {
|
|
mBuilder.append(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void flushBuilder() {
|
|
if (mBuilder.length() > 0) {
|
|
Log.v("GLSurfaceView", mBuilder.toString());
|
|
mBuilder.delete(0, mBuilder.length());
|
|
}
|
|
}
|
|
|
|
private StringBuilder mBuilder = new StringBuilder();
|
|
}
|
|
|
|
private static final Semaphore sEglSemaphore = new Semaphore(1);
|
|
private boolean mSizeChanged = true;
|
|
|
|
private GLThread mGLThread;
|
|
private GLWrapper mGLWrapper;
|
|
private int mDebugFlags;
|
|
}
|