• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OGLESContext()100     public OGLESContext() {
101     }
102 
103     @Override
getType()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      */
createView(Activity activity)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      */
createView(AndroidInput view)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      */
createView(AndroidInput view, ConfigType configType, boolean eglConfigVerboseLogging)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
onSurfaceCreated(GL10 gl, EGLConfig cfg)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 
initInThread()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      */
deinitInThread()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 
applySettingsToRenderer(OGLESShaderRenderer renderer, AppSettings settings)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 
applySettings(AppSettings settings)302     protected void applySettings(AppSettings settings) {
303         setSettings(settings);
304         if (renderer != null) {
305             applySettingsToRenderer(renderer, this.settings);
306         }
307     }
308 
309     @Override
setSettings(AppSettings settings)310     public void setSettings(AppSettings settings) {
311         this.settings.copyFrom(settings);
312     }
313 
314     @Override
setSystemListener(SystemListener listener)315     public void setSystemListener(SystemListener listener) {
316         this.listener = listener;
317     }
318 
319     @Override
getSettings()320     public AppSettings getSettings() {
321         return settings;
322     }
323 
324     @Override
getRenderer()325     public com.jme3.renderer.Renderer getRenderer() {
326         return renderer;
327     }
328 
329     @Override
getMouseInput()330     public MouseInput getMouseInput() {
331         return new DummyMouseInput();
332     }
333 
334     @Override
getKeyInput()335     public KeyInput getKeyInput() {
336         return new DummyKeyInput();
337     }
338 
339     @Override
getJoyInput()340     public JoyInput getJoyInput() {
341         return null;
342     }
343 
344     @Override
getTouchInput()345     public TouchInput getTouchInput() {
346         return view;
347     }
348 
349     @Override
getTimer()350     public Timer getTimer() {
351         return timer;
352     }
353 
354     @Override
setTitle(String title)355     public void setTitle(String title) {
356     }
357 
358     @Override
isCreated()359     public boolean isCreated() {
360         return created.get();
361     }
362 
363     @Override
setAutoFlushFrames(boolean enabled)364     public void setAutoFlushFrames(boolean enabled) {
365         this.autoFlush = enabled;
366     }
367 
368     // SystemListener:reshape
369     @Override
onSurfaceChanged(GL10 gl, int width, int height)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
onDrawFrame(GL10 gl)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
isRenderable()423     public boolean isRenderable() {
424         return renderable.get();
425     }
426 
427     @Override
create(boolean waitFor)428     public void create(boolean waitFor) {
429         if (waitFor) {
430             waitFor(true);
431         }
432     }
433 
create()434     public void create() {
435         create(false);
436     }
437 
438     @Override
restart()439     public void restart() {
440     }
441 
442     @Override
destroy(boolean waitFor)443     public void destroy(boolean waitFor) {
444         needClose.set(true);
445         if (waitFor) {
446             waitFor(false);
447         }
448     }
449 
destroy()450     public void destroy() {
451         destroy(true);
452     }
453 
waitFor(boolean createdVal)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 
getClientOpenGLESVersion()463     public int getClientOpenGLESVersion() {
464         return clientOpenGLESVersion;
465     }
466 
requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener)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