/* * Copyright (c) 2003-2009 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.system.android; import android.app.Activity; import android.content.Context; import android.opengl.GLSurfaceView; import android.view.SurfaceHolder; import com.jme3.app.AndroidHarness; import com.jme3.app.Application; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.android.AndroidInput; import com.jme3.input.controls.TouchTrigger; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; import com.jme3.renderer.android.OGLESShaderRenderer; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; import com.jme3.system.SystemListener; import com.jme3.system.Timer; import com.jme3.system.android.AndroidConfigChooser.ConfigType; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.opengles.GL10; public class OGLESContext implements JmeContext, GLSurfaceView.Renderer { private static final Logger logger = Logger.getLogger(OGLESContext.class.getName()); protected final AtomicBoolean created = new AtomicBoolean(false); protected final AtomicBoolean renderable = new AtomicBoolean(false); protected final AtomicBoolean needClose = new AtomicBoolean(false); protected final AppSettings settings = new AppSettings(true); /* * >= OpenGL ES 2.0 (Android 2.2+) */ protected OGLESShaderRenderer renderer; protected Timer timer; protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInput view; private boolean firstDrawFrame = true; //protected int minFrameDuration = 1000 / frameRate; // Set a max FPS of 33 protected int minFrameDuration = 0; // No FPS cap /** * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 | * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0 */ protected int clientOpenGLESVersion = 1; protected boolean verboseLogging = false; final private String ESCAPE_EVENT = "TouchEscape"; public OGLESContext() { } @Override public Type getType() { return Type.Display; } /** * createView * * @param activity The Android activity which is parent for the * GLSurfaceView * @return GLSurfaceView The newly created view */ public GLSurfaceView createView(Activity activity) { return createView(new AndroidInput(activity)); } /** * createView * * @param view The Android input which will be used as the GLSurfaceView for * this context * @return GLSurfaceView The newly created view */ public GLSurfaceView createView(AndroidInput view) { return createView(view, ConfigType.FASTEST, false); } /** * createView initializes the GLSurfaceView * * @param view The Android input which will be used as the GLSurfaceView for * this context * @param configType ConfigType.FASTEST (Default) | ConfigType.LEGACY | * ConfigType.BEST * @param eglConfigVerboseLogging if true show all found configs * @return GLSurfaceView The newly created view */ public GLSurfaceView createView(AndroidInput view, ConfigType configType, boolean eglConfigVerboseLogging) { // Start to set up the view this.view = view; verboseLogging = eglConfigVerboseLogging; if (configType == ConfigType.LEGACY) { // Hardcoded egl setup clientOpenGLESVersion = 2; view.setEGLContextClientVersion(2); //RGB565, Depth16 view.setEGLConfigChooser(5, 6, 5, 0, 16, 0); logger.info("ConfigType.LEGACY using RGB565"); } else { EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int[2]; if (egl.eglInitialize(display, version) == true) { logger.info("Display EGL Version: " + version[0] + "." + version[1]); } try { // Create a config chooser AndroidConfigChooser configChooser = new AndroidConfigChooser(configType, eglConfigVerboseLogging); // Init chooser if (!configChooser.findConfig(egl, display)) { listener.handleError("Unable to find suitable EGL config", null); return null; } clientOpenGLESVersion = configChooser.getClientOpenGLESVersion(); if (clientOpenGLESVersion < 2) { listener.handleError("OpenGL ES 2.0 is not supported on this device", null); return null; } // Requesting client version from GLSurfaceView which is extended by // AndroidInput. view.setEGLContextClientVersion(clientOpenGLESVersion); view.setEGLConfigChooser(configChooser); view.getHolder().setFormat(configChooser.getPixelFormat()); } finally { if (display != null) { egl.eglTerminate(display); } } } view.setFocusableInTouchMode(true); view.setFocusable(true); view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); view.setRenderer(this); return view; } // renderer:initialize @Override public void onSurfaceCreated(GL10 gl, EGLConfig cfg) { if (created.get() && renderer != null) { renderer.resetGLObjects(); } else { if (!created.get()) { logger.info("GL Surface created, doing JME3 init"); initInThread(); } else { logger.warning("GL Surface already created"); } } } protected void initInThread() { created.set(true); logger.info("OGLESContext create"); logger.info("Running on thread: " + Thread.currentThread().getName()); final Context ctx = this.view.getContext(); // Setup unhandled Exception Handler if (ctx instanceof AndroidHarness) { Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread thread, Throwable thrown) { ((AndroidHarness) ctx).handleError("Exception thrown in " + thread.toString(), thrown); } }); } else { Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread thread, Throwable thrown) { listener.handleError("Exception thrown in " + thread.toString(), thrown); } }); } if (clientOpenGLESVersion < 2) { throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device"); } timer = new AndroidTimer(); renderer = new OGLESShaderRenderer(); renderer.setUseVA(true); renderer.setVerboseLogging(verboseLogging); renderer.initialize(); listener.initialize(); // Setup exit hook if (ctx instanceof AndroidHarness) { Application app = ((AndroidHarness) ctx).getJmeApplication(); if (app.getInputManager() != null) { app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); app.getInputManager().addListener((AndroidHarness) ctx, new String[]{ESCAPE_EVENT}); } } needClose.set(false); renderable.set(true); } /** * De-initialize in the OpenGL thread. */ protected void deinitInThread() { if (renderable.get()) { created.set(false); if (renderer != null) { renderer.cleanup(); } listener.destroy(); listener = null; renderer = null; timer = null; // do android specific cleaning here logger.info("Display destroyed."); renderable.set(false); } } protected void applySettingsToRenderer(OGLESShaderRenderer renderer, AppSettings settings) { logger.warning("setSettings.USE_VA: [" + settings.getBoolean("USE_VA") + "]"); logger.warning("setSettings.VERBOSE_LOGGING: [" + settings.getBoolean("VERBOSE_LOGGING") + "]"); renderer.setUseVA(settings.getBoolean("USE_VA")); renderer.setVerboseLogging(settings.getBoolean("VERBOSE_LOGGING")); } protected void applySettings(AppSettings settings) { setSettings(settings); if (renderer != null) { applySettingsToRenderer(renderer, this.settings); } } @Override public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); } @Override public void setSystemListener(SystemListener listener) { this.listener = listener; } @Override public AppSettings getSettings() { return settings; } @Override public com.jme3.renderer.Renderer getRenderer() { return renderer; } @Override public MouseInput getMouseInput() { return new DummyMouseInput(); } @Override public KeyInput getKeyInput() { return new DummyKeyInput(); } @Override public JoyInput getJoyInput() { return null; } @Override public TouchInput getTouchInput() { return view; } @Override public Timer getTimer() { return timer; } @Override public void setTitle(String title) { } @Override public boolean isCreated() { return created.get(); } @Override public void setAutoFlushFrames(boolean enabled) { this.autoFlush = enabled; } // SystemListener:reshape @Override public void onSurfaceChanged(GL10 gl, int width, int height) { logger.info("GL Surface changed, width: " + width + " height: " + height); settings.setResolution(width, height); listener.reshape(width, height); } // SystemListener:update @Override public void onDrawFrame(GL10 gl) { if (needClose.get()) { deinitInThread(); return; } if (renderable.get()) { if (!created.get()) { throw new IllegalStateException("onDrawFrame without create"); } long milliStart = System.currentTimeMillis(); listener.update(); // call to AndroidHarness to remove the splash screen, if present. // call after listener.update() to make sure no gap between // splash screen going away and app display being shown. if (firstDrawFrame) { final Context ctx = this.view.getContext(); if (ctx instanceof AndroidHarness) { ((AndroidHarness) ctx).removeSplashScreen(); } firstDrawFrame = false; } if (autoFlush) { renderer.onFrame(); } long milliDelta = System.currentTimeMillis() - milliStart; // Enforce a FPS cap if (milliDelta < minFrameDuration) { //logger.log(Level.INFO, "Time per frame {0}", milliDelta); try { Thread.sleep(minFrameDuration - milliDelta); } catch (InterruptedException e) { } } } } @Override public boolean isRenderable() { return renderable.get(); } @Override public void create(boolean waitFor) { if (waitFor) { waitFor(true); } } public void create() { create(false); } @Override public void restart() { } @Override public void destroy(boolean waitFor) { needClose.set(true); if (waitFor) { waitFor(false); } } public void destroy() { destroy(true); } protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); } catch (InterruptedException ex) { } } } public int getClientOpenGLESVersion() { return clientOpenGLESVersion; } }