1 /* 2 * Copyright (c) 2003-2009 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.system.android; 33 34 import android.app.Activity; 35 import android.app.AlertDialog; 36 import android.content.Context; 37 import android.content.DialogInterface; 38 import android.opengl.GLSurfaceView; 39 import android.text.InputType; 40 import android.view.Gravity; 41 import android.view.SurfaceHolder; 42 import android.view.ViewGroup.LayoutParams; 43 import android.widget.EditText; 44 import android.widget.FrameLayout; 45 import com.jme3.app.AndroidHarness; 46 import com.jme3.app.Application; 47 import com.jme3.input.JoyInput; 48 import com.jme3.input.KeyInput; 49 import com.jme3.input.MouseInput; 50 import com.jme3.input.SoftTextDialogInput; 51 import com.jme3.input.TouchInput; 52 import com.jme3.input.android.AndroidInput; 53 import com.jme3.input.controls.SoftTextDialogInputListener; 54 import com.jme3.input.controls.TouchTrigger; 55 import com.jme3.input.dummy.DummyKeyInput; 56 import com.jme3.input.dummy.DummyMouseInput; 57 import com.jme3.renderer.android.OGLESShaderRenderer; 58 import com.jme3.system.AppSettings; 59 import com.jme3.system.JmeContext; 60 import com.jme3.system.JmeSystem; 61 import com.jme3.system.SystemListener; 62 import com.jme3.system.Timer; 63 import com.jme3.system.android.AndroidConfigChooser.ConfigType; 64 import java.util.concurrent.atomic.AtomicBoolean; 65 import java.util.logging.Level; 66 import java.util.logging.Logger; 67 import javax.microedition.khronos.egl.EGL10; 68 import javax.microedition.khronos.egl.EGLConfig; 69 import javax.microedition.khronos.egl.EGLContext; 70 import javax.microedition.khronos.egl.EGLDisplay; 71 import javax.microedition.khronos.opengles.GL10; 72 73 public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTextDialogInput { 74 75 private static final Logger logger = Logger.getLogger(OGLESContext.class.getName()); 76 protected final AtomicBoolean created = new AtomicBoolean(false); 77 protected final AtomicBoolean renderable = new AtomicBoolean(false); 78 protected final AtomicBoolean needClose = new AtomicBoolean(false); 79 protected final AppSettings settings = new AppSettings(true); 80 81 /* 82 * >= OpenGL ES 2.0 (Android 2.2+) 83 */ 84 protected OGLESShaderRenderer renderer; 85 protected Timer timer; 86 protected SystemListener listener; 87 protected boolean autoFlush = true; 88 protected AndroidInput view; 89 private boolean firstDrawFrame = true; 90 //protected int minFrameDuration = 1000 / frameRate; // Set a max FPS of 33 91 protected int minFrameDuration = 0; // No FPS cap 92 /** 93 * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 | 94 * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0 95 */ 96 protected int clientOpenGLESVersion = 1; 97 protected boolean verboseLogging = false; 98 final private String ESCAPE_EVENT = "TouchEscape"; 99 100 public OGLESContext() { 101 } 102 103 @Override 104 public Type getType() { 105 return Type.Display; 106 } 107 108 /** 109 * <code>createView</code> 110 * 111 * @param activity The Android activity which is parent for the 112 * GLSurfaceView 113 * @return GLSurfaceView The newly created view 114 */ 115 public GLSurfaceView createView(Activity activity) { 116 return createView(new AndroidInput(activity)); 117 } 118 119 /** 120 * <code>createView</code> 121 * 122 * @param view The Android input which will be used as the GLSurfaceView for 123 * this context 124 * @return GLSurfaceView The newly created view 125 */ 126 public GLSurfaceView createView(AndroidInput view) { 127 return createView(view, ConfigType.FASTEST, false); 128 } 129 130 /** 131 * <code>createView</code> initializes the GLSurfaceView 132 * 133 * @param view The Android input which will be used as the GLSurfaceView for 134 * this context 135 * @param configType ConfigType.FASTEST (Default) | ConfigType.LEGACY | 136 * ConfigType.BEST 137 * @param eglConfigVerboseLogging if true show all found configs 138 * @return GLSurfaceView The newly created view 139 */ 140 public GLSurfaceView createView(AndroidInput view, ConfigType configType, boolean eglConfigVerboseLogging) { 141 // Start to set up the view 142 this.view = view; 143 verboseLogging = eglConfigVerboseLogging; 144 145 if (configType == ConfigType.LEGACY) { 146 // Hardcoded egl setup 147 clientOpenGLESVersion = 2; 148 view.setEGLContextClientVersion(2); 149 //RGB565, Depth16 150 view.setEGLConfigChooser(5, 6, 5, 0, 16, 0); 151 logger.info("ConfigType.LEGACY using RGB565"); 152 } else { 153 EGL10 egl = (EGL10) EGLContext.getEGL(); 154 EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 155 156 int[] version = new int[2]; 157 if (egl.eglInitialize(display, version) == true) { 158 logger.log(Level.INFO, "Display EGL Version: {0}.{1}", new Object[]{version[0], version[1]}); 159 } 160 161 try { 162 // Create a config chooser 163 AndroidConfigChooser configChooser = new AndroidConfigChooser(configType); 164 // Init chooser 165 if (!configChooser.findConfig(egl, display)) { 166 listener.handleError("Unable to find suitable EGL config", null); 167 return null; 168 } 169 170 clientOpenGLESVersion = configChooser.getClientOpenGLESVersion(); 171 if (clientOpenGLESVersion < 2) { 172 listener.handleError("OpenGL ES 2.0 is not supported on this device", null); 173 return null; 174 } 175 176 // Requesting client version from GLSurfaceView which is extended by 177 // AndroidInput. 178 view.setEGLContextClientVersion(clientOpenGLESVersion); 179 view.setEGLConfigChooser(configChooser); 180 view.getHolder().setFormat(configChooser.getPixelFormat()); 181 } finally { 182 if (display != null) { 183 egl.eglTerminate(display); 184 } 185 } 186 } 187 188 view.setFocusableInTouchMode(true); 189 view.setFocusable(true); 190 view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); 191 view.setRenderer(this); 192 193 return view; 194 } 195 196 // renderer:initialize 197 @Override 198 public void onSurfaceCreated(GL10 gl, EGLConfig cfg) { 199 200 if (created.get() && renderer != null) { 201 renderer.resetGLObjects(); 202 } else { 203 if (!created.get()) { 204 logger.info("GL Surface created, doing JME3 init"); 205 initInThread(); 206 } else { 207 logger.warning("GL Surface already created"); 208 } 209 } 210 } 211 212 protected void initInThread() { 213 created.set(true); 214 215 logger.info("OGLESContext create"); 216 logger.info("Running on thread: " + Thread.currentThread().getName()); 217 218 final Context ctx = this.view.getContext(); 219 220 // Setup unhandled Exception Handler 221 if (ctx instanceof AndroidHarness) { 222 Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 223 224 public void uncaughtException(Thread thread, Throwable thrown) { 225 ((AndroidHarness) ctx).handleError("Exception thrown in " + thread.toString(), thrown); 226 } 227 }); 228 } else { 229 Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 230 231 public void uncaughtException(Thread thread, Throwable thrown) { 232 listener.handleError("Exception thrown in " + thread.toString(), thrown); 233 } 234 }); 235 } 236 237 if (clientOpenGLESVersion < 2) { 238 throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device"); 239 } 240 241 timer = new AndroidTimer(); 242 renderer = new OGLESShaderRenderer(); 243 244 renderer.setUseVA(true); 245 renderer.setVerboseLogging(verboseLogging); 246 247 renderer.initialize(); 248 listener.initialize(); 249 250 // Setup exit hook 251 if (ctx instanceof AndroidHarness) { 252 Application app = ((AndroidHarness) ctx).getJmeApplication(); 253 if (app.getInputManager() != null) { 254 app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); 255 app.getInputManager().addListener((AndroidHarness) ctx, new String[]{ESCAPE_EVENT}); 256 } 257 } 258 259 JmeSystem.setSoftTextDialogInput(this); 260 261 needClose.set(false); 262 renderable.set(true); 263 } 264 265 /** 266 * De-initialize in the OpenGL thread. 267 */ 268 protected void deinitInThread() { 269 if (renderable.get()) { 270 created.set(false); 271 if (renderer != null) { 272 renderer.cleanup(); 273 } 274 275 listener.destroy(); 276 277 listener = null; 278 renderer = null; 279 timer = null; 280 281 // do android specific cleaning here 282 logger.info("Display destroyed."); 283 284 renderable.set(false); 285 final Context ctx = this.view.getContext(); 286 if (ctx instanceof AndroidHarness) { 287 AndroidHarness harness = (AndroidHarness) ctx; 288 if (harness.isFinishOnAppStop()) { 289 harness.finish(); 290 } 291 } 292 } 293 } 294 295 protected void applySettingsToRenderer(OGLESShaderRenderer renderer, AppSettings settings) { 296 logger.warning("setSettings.USE_VA: [" + settings.getBoolean("USE_VA") + "]"); 297 logger.warning("setSettings.VERBOSE_LOGGING: [" + settings.getBoolean("VERBOSE_LOGGING") + "]"); 298 renderer.setUseVA(settings.getBoolean("USE_VA")); 299 renderer.setVerboseLogging(settings.getBoolean("VERBOSE_LOGGING")); 300 } 301 302 protected void applySettings(AppSettings settings) { 303 setSettings(settings); 304 if (renderer != null) { 305 applySettingsToRenderer(renderer, this.settings); 306 } 307 } 308 309 @Override 310 public void setSettings(AppSettings settings) { 311 this.settings.copyFrom(settings); 312 } 313 314 @Override 315 public void setSystemListener(SystemListener listener) { 316 this.listener = listener; 317 } 318 319 @Override 320 public AppSettings getSettings() { 321 return settings; 322 } 323 324 @Override 325 public com.jme3.renderer.Renderer getRenderer() { 326 return renderer; 327 } 328 329 @Override 330 public MouseInput getMouseInput() { 331 return new DummyMouseInput(); 332 } 333 334 @Override 335 public KeyInput getKeyInput() { 336 return new DummyKeyInput(); 337 } 338 339 @Override 340 public JoyInput getJoyInput() { 341 return null; 342 } 343 344 @Override 345 public TouchInput getTouchInput() { 346 return view; 347 } 348 349 @Override 350 public Timer getTimer() { 351 return timer; 352 } 353 354 @Override 355 public void setTitle(String title) { 356 } 357 358 @Override 359 public boolean isCreated() { 360 return created.get(); 361 } 362 363 @Override 364 public void setAutoFlushFrames(boolean enabled) { 365 this.autoFlush = enabled; 366 } 367 368 // SystemListener:reshape 369 @Override 370 public void onSurfaceChanged(GL10 gl, int width, int height) { 371 logger.info("GL Surface changed, width: " + width + " height: " + height); 372 settings.setResolution(width, height); 373 listener.reshape(width, height); 374 } 375 376 // SystemListener:update 377 @Override 378 public void onDrawFrame(GL10 gl) { 379 if (needClose.get()) { 380 deinitInThread(); 381 return; 382 } 383 384 if (renderable.get()) { 385 if (!created.get()) { 386 throw new IllegalStateException("onDrawFrame without create"); 387 } 388 389 long milliStart = System.currentTimeMillis(); 390 391 listener.update(); 392 393 // call to AndroidHarness to remove the splash screen, if present. 394 // call after listener.update() to make sure no gap between 395 // splash screen going away and app display being shown. 396 if (firstDrawFrame) { 397 final Context ctx = this.view.getContext(); 398 if (ctx instanceof AndroidHarness) { 399 ((AndroidHarness) ctx).removeSplashScreen(); 400 } 401 firstDrawFrame = false; 402 } 403 404 if (autoFlush) { 405 renderer.onFrame(); 406 } 407 408 long milliDelta = System.currentTimeMillis() - milliStart; 409 410 // Enforce a FPS cap 411 if (milliDelta < minFrameDuration) { 412 //logger.log(Level.INFO, "Time per frame {0}", milliDelta); 413 try { 414 Thread.sleep(minFrameDuration - milliDelta); 415 } catch (InterruptedException e) { 416 } 417 } 418 419 } 420 } 421 422 @Override 423 public boolean isRenderable() { 424 return renderable.get(); 425 } 426 427 @Override 428 public void create(boolean waitFor) { 429 if (waitFor) { 430 waitFor(true); 431 } 432 } 433 434 public void create() { 435 create(false); 436 } 437 438 @Override 439 public void restart() { 440 } 441 442 @Override 443 public void destroy(boolean waitFor) { 444 needClose.set(true); 445 if (waitFor) { 446 waitFor(false); 447 } 448 } 449 450 public void destroy() { 451 destroy(true); 452 } 453 454 protected void waitFor(boolean createdVal) { 455 while (renderable.get() != createdVal) { 456 try { 457 Thread.sleep(10); 458 } catch (InterruptedException ex) { 459 } 460 } 461 } 462 463 public int getClientOpenGLESVersion() { 464 return clientOpenGLESVersion; 465 } 466 467 public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) { 468 logger.log(Level.INFO, "requestDialog: title: {0}, initialValue: {1}", 469 new Object[]{title, initialValue}); 470 471 JmeAndroidSystem.getActivity().runOnUiThread(new Runnable() { 472 473 @Override 474 public void run() { 475 476 final FrameLayout layoutTextDialogInput = new FrameLayout(JmeAndroidSystem.getActivity()); 477 final EditText editTextDialogInput = new EditText(JmeAndroidSystem.getActivity()); 478 editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); 479 editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); 480 editTextDialogInput.setPadding(20, 20, 20, 20); 481 editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); 482 483 editTextDialogInput.setText(initialValue); 484 485 switch (id) { 486 case SoftTextDialogInput.TEXT_ENTRY_DIALOG: 487 488 editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT); 489 break; 490 491 case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG: 492 493 editTextDialogInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); 494 break; 495 496 case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG: 497 498 editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE); 499 break; 500 501 default: 502 break; 503 } 504 505 layoutTextDialogInput.addView(editTextDialogInput); 506 507 AlertDialog dialogTextInput = new AlertDialog.Builder(JmeAndroidSystem.getActivity()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK", 508 new DialogInterface.OnClickListener() { 509 510 public void onClick(DialogInterface dialog, int whichButton) { 511 /* User clicked OK, send COMPLETE action 512 * and text */ 513 listener.onSoftText(SoftTextDialogInputListener.COMPLETE, editTextDialogInput.getText().toString()); 514 } 515 }).setNegativeButton("Cancel", 516 new DialogInterface.OnClickListener() { 517 518 public void onClick(DialogInterface dialog, int whichButton) { 519 /* User clicked CANCEL, send CANCEL action 520 * and text */ 521 listener.onSoftText(SoftTextDialogInputListener.CANCEL, editTextDialogInput.getText().toString()); 522 } 523 }).create(); 524 525 dialogTextInput.show(); 526 } 527 }); 528 } 529 } 530