/*
* Copyright (C) 2010 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 org.emdev.ui.gl;
import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.os.Process;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import org.emdev.common.log.LogContext;
import org.emdev.common.log.LogManager;
// The root component of all <code>GLView</code>s. The rendering is done in GL
// thread while the event handling is done in the main thread. To synchronize
// the two threads, the entry points of this package need to synchronize on the
// <code>GLRootView</code> instance unless it can be proved that the rendering
// thread won't access the same thing as the method. The entry points include:
// (1) The public methods of HeadUpDisplay
// (2) The public methods of CameraHeadUpDisplay
// (3) The overridden methods in GLRootView.
public class GLRootView extends GLSurfaceView implements GLSurfaceView.Renderer {
private static final LogContext LCTX = LogManager.root().lctx("GLRootView");
public static final int FLAG_INITIALIZED = 1;
public static final int FLAG_NEED_LAYOUT = 2;
protected GL11 mGL;
protected GLCanvas mCanvas;
protected int mFlags = FLAG_NEED_LAYOUT;
protected volatile boolean mRenderRequested = false;
protected final ReentrantLock mRenderLock = new ReentrantLock();
protected final Condition mFreezeCondition = mRenderLock.newCondition();
protected boolean mFreeze;
protected long mLastDrawFinishTime;
protected boolean mInDownState = false;
protected boolean mFirstDraw = true;
public GLRootView(final Context context) {
this(context, null);
}
public GLRootView(final Context context, final AttributeSet attrs) {
super(context, attrs);
mFlags |= FLAG_INITIALIZED;
setEGLConfigChooser(GLConfiguration.getConfigChooser());
setRenderer(this);
if (GLConfiguration.use8888) {
getHolder().setFormat(PixelFormat.RGBA_8888);
} else {
getHolder().setFormat(PixelFormat.RGB_565);
}
}
@Override
public void requestRender() {
if (mRenderRequested) {
return;
}
mRenderRequested = true;
super.requestRender();
}
public void requestLayoutContentPane() {
mRenderLock.lock();
try {
if ((mFlags & FLAG_NEED_LAYOUT) != 0) {
return;
}
// "View" system will invoke onLayout() for initialization(bug ?), we
// have to ignore it since the GLThread is not ready yet.
if ((mFlags & FLAG_INITIALIZED) == 0) {
return;
}
mFlags |= FLAG_NEED_LAYOUT;
requestRender();
} finally {
mRenderLock.unlock();
}
}
private void layoutContentPane() {
mFlags &= ~FLAG_NEED_LAYOUT;
final int w = getWidth();
final int h = getHeight();
// Do the actual layout.
LCTX.i("layout content pane " + w + "x" + h);
}
@Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
if (changed) {
requestLayoutContentPane();
}
}
/**
* Called when the context is created, possibly after automatic destruction.
*/
// This is a GLSurfaceView.Renderer callback
@Override
public void onSurfaceCreated(final GL10 gl1, final EGLConfig config) {
final GL11 gl = (GL11) gl1;
if (mGL != null) {
// The GL Object has changed
LCTX.i("GLObject has changed from " + mGL + " to " + gl);
}
mRenderLock.lock();
try {
mGL = gl;
mCanvas = new GLCanvasImpl(gl);
BasicTexture.invalidateAllTextures();
} finally {
mRenderLock.unlock();
}
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
/**
* Called when the OpenGL surface is recreated without destroying the
* context.
*/
// This is a GLSurfaceView.Renderer callback
@Override
public void onSurfaceChanged(final GL10 gl1, final int width, final int height) {
LCTX.i("onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString());
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
mCanvas.setSize(width, height);
}
@Override
public void onDrawFrame(final GL10 gl) {
mRenderLock.lock();
while (mFreeze) {
mFreezeCondition.awaitUninterruptibly();
}
try {
onDrawFrameLocked(gl);
} finally {
mRenderLock.unlock();
}
}
protected void onDrawFrameLocked(final GL10 gl) {
// release the unbound textures and deleted buffers.
mCanvas.deleteRecycledResources();
// reset texture upload limit
UploadedTexture.resetUploadLimit();
mRenderRequested = false;
if ((mFlags & FLAG_NEED_LAYOUT) != 0) {
layoutContentPane();
}
mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
// render
draw(this.mCanvas);
mCanvas.restore();
if (UploadedTexture.uploadLimitReached()) {
requestRender();
}
}
protected void draw(final GLCanvas canvas) {
}
public void lockRenderThread() {
mRenderLock.lock();
}
public void unlockRenderThread() {
mRenderLock.unlock();
}
@Override
public void onPause() {
unfreeze();
super.onPause();
}
public void freeze() {
mRenderLock.lock();
mFreeze = true;
mRenderLock.unlock();
}
public void unfreeze() {
mRenderLock.lock();
mFreeze = false;
mFreezeCondition.signalAll();
mRenderLock.unlock();
}
// We need to unfreeze in the following methods and in onPause().
// These methods will wait on GLThread. If we have freezed the GLRootView,
// the GLThread will wait on main thread to call unfreeze and cause dead
// lock.
@Override
public void surfaceChanged(final SurfaceHolder holder, final int format, final int w, final int h) {
unfreeze();
super.surfaceChanged(holder, format, w, h);
}
@Override
public void surfaceCreated(final SurfaceHolder holder) {
unfreeze();
super.surfaceCreated(holder);
}
@Override
public void surfaceDestroyed(final SurfaceHolder holder) {
unfreeze();
super.surfaceDestroyed(holder);
}
@Override
protected void onDetachedFromWindow() {
unfreeze();
super.onDetachedFromWindow();
}
@Override
protected void finalize() throws Throwable {
try {
unfreeze();
} finally {
super.finalize();
}
}
}