OGLESContext.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. * Copyright (c) 2009-2018 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. import android.app.ActivityManager;
  34. import android.app.AlertDialog;
  35. import android.content.Context;
  36. import android.content.DialogInterface;
  37. import android.content.pm.ConfigurationInfo;
  38. import android.graphics.PixelFormat;
  39. import android.opengl.GLSurfaceView;
  40. import android.os.Build;
  41. import android.text.InputType;
  42. import android.view.Gravity;
  43. import android.view.View;
  44. import android.view.ViewGroup.LayoutParams;
  45. import android.widget.EditText;
  46. import android.widget.FrameLayout;
  47. import com.jme3.input.*;
  48. import com.jme3.input.android.AndroidInputHandler;
  49. import com.jme3.input.android.AndroidInputHandler14;
  50. import com.jme3.input.controls.SoftTextDialogInputListener;
  51. import com.jme3.input.dummy.DummyKeyInput;
  52. import com.jme3.input.dummy.DummyMouseInput;
  53. import com.jme3.renderer.android.AndroidGL;
  54. import com.jme3.renderer.opengl.GL;
  55. import com.jme3.renderer.opengl.GLES_30;
  56. import com.jme3.renderer.opengl.GLDebugES;
  57. import com.jme3.renderer.opengl.GLExt;
  58. import com.jme3.renderer.opengl.GLFbo;
  59. import com.jme3.renderer.opengl.GLRenderer;
  60. import com.jme3.renderer.opengl.GLTracer;
  61. import com.jme3.system.*;
  62. import com.jme3.util.AndroidBufferAllocator;
  63. import com.jme3.util.BufferAllocatorFactory;
  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.EGLConfig;
  68. import javax.microedition.khronos.opengles.GL10;
  69. public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTextDialogInput {
  70. private static final Logger logger = Logger.getLogger(OGLESContext.class.getName());
  71. protected final AtomicBoolean created = new AtomicBoolean(false);
  72. protected final AtomicBoolean renderable = new AtomicBoolean(false);
  73. protected final AtomicBoolean needClose = new AtomicBoolean(false);
  74. protected AppSettings settings = new AppSettings(true);
  75. protected GLRenderer renderer;
  76. protected Timer timer;
  77. protected SystemListener listener;
  78. protected boolean autoFlush = true;
  79. protected AndroidInputHandler androidInput;
  80. protected long minFrameDuration = 0; // No FPS cap
  81. protected long lastUpdateTime = 0;
  82. static {
  83. final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;
  84. if (System.getProperty(implementation) == null) {
  85. System.setProperty(implementation, AndroidBufferAllocator.class.getName());
  86. }
  87. }
  88. public OGLESContext() {
  89. }
  90. @Override
  91. public Type getType() {
  92. return Type.Display;
  93. }
  94. /**
  95. * <code>createView</code> creates the GLSurfaceView that the renderer will
  96. * draw to. <p> The result GLSurfaceView will receive input events and
  97. * forward them to the Application. Any rendering will be done into the
  98. * GLSurfaceView. Only one GLSurfaceView can be created at this time. The
  99. * given configType specifies how to determine the display configuration.
  100. *
  101. * @return GLSurfaceView The newly created view
  102. */
  103. public GLSurfaceView createView(Context context) {
  104. ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  105. ConfigurationInfo info = am.getDeviceConfigurationInfo();
  106. // NOTE: We assume all ICS devices have OpenGL ES 2.0.
  107. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  108. // below 4.0, check OpenGL ES 2.0 support.
  109. if (info.reqGlEsVersion < 0x20000) {
  110. throw new UnsupportedOperationException("OpenGL ES 2.0 or better is not supported on this device");
  111. }
  112. } else if (Build.VERSION.SDK_INT < 9){
  113. throw new UnsupportedOperationException("jME3 requires Android 2.3 or later");
  114. }
  115. // Start to set up the view
  116. GLSurfaceView view = new GLSurfaceView(context);
  117. logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
  118. if (androidInput == null) {
  119. if (Build.VERSION.SDK_INT >= 14) {
  120. androidInput = new AndroidInputHandler14();
  121. } else if (Build.VERSION.SDK_INT >= 9){
  122. androidInput = new AndroidInputHandler();
  123. }
  124. }
  125. androidInput.setView(view);
  126. androidInput.loadSettings(settings);
  127. // setEGLContextClientVersion must be set before calling setRenderer
  128. // this means it cannot be set in AndroidConfigChooser (too late)
  129. // use proper openGL ES version
  130. view.setEGLContextClientVersion(info.reqGlEsVersion>>16);
  131. view.setFocusableInTouchMode(true);
  132. view.setFocusable(true);
  133. // setFormat must be set before AndroidConfigChooser is called by the surfaceview.
  134. // if setFormat is called after ConfigChooser is called, then execution
  135. // stops at the setFormat call without a crash.
  136. // We look at the user setting for alpha bits and set the surfaceview
  137. // PixelFormat to either Opaque, Transparent, or Translucent.
  138. // ConfigChooser will do its best to honor the alpha requested by the user
  139. // For best rendering performance, use Opaque (alpha bits = 0).
  140. int curAlphaBits = settings.getAlphaBits();
  141. logger.log(Level.FINE, "curAlphaBits: {0}", curAlphaBits);
  142. if (curAlphaBits >= 8) {
  143. logger.log(Level.FINE, "Pixel Format: TRANSLUCENT");
  144. view.getHolder().setFormat(PixelFormat.TRANSLUCENT);
  145. view.setZOrderOnTop(true);
  146. } else if (curAlphaBits >= 1) {
  147. logger.log(Level.FINE, "Pixel Format: TRANSPARENT");
  148. view.getHolder().setFormat(PixelFormat.TRANSPARENT);
  149. } else {
  150. logger.log(Level.FINE, "Pixel Format: OPAQUE");
  151. view.getHolder().setFormat(PixelFormat.OPAQUE);
  152. }
  153. AndroidConfigChooser configChooser = new AndroidConfigChooser(settings);
  154. view.setEGLConfigChooser(configChooser);
  155. view.setRenderer(this);
  156. // Attempt to preserve the EGL Context on app pause/resume.
  157. // Not destroying and recreating the EGL context
  158. // will help with resume time by reusing the existing context to avoid
  159. // reloading all the OpenGL objects.
  160. if (Build.VERSION.SDK_INT >= 11) {
  161. view.setPreserveEGLContextOnPause(true);
  162. }
  163. return view;
  164. }
  165. // renderer:initialize
  166. @Override
  167. public void onSurfaceCreated(GL10 gl, EGLConfig cfg) {
  168. if (created.get() && renderer != null) {
  169. renderer.resetGLObjects();
  170. } else {
  171. if (!created.get()) {
  172. logger.fine("GL Surface created, initializing JME3 renderer");
  173. initInThread();
  174. } else {
  175. logger.warning("GL Surface already created");
  176. }
  177. }
  178. }
  179. protected void initInThread() {
  180. created.set(true);
  181. logger.fine("OGLESContext create");
  182. logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
  183. // Setup unhandled Exception Handler
  184. Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
  185. public void uncaughtException(Thread thread, Throwable thrown) {
  186. listener.handleError("Exception thrown in " + thread.toString(), thrown);
  187. }
  188. });
  189. timer = new NanoTimer();
  190. Object gl = new AndroidGL();
  191. if (settings.getBoolean("GraphicsDebug")) {
  192. gl = new GLDebugES((GL) gl, (GLExt) gl, (GLFbo) gl);
  193. }
  194. if (settings.getBoolean("GraphicsTrace")) {
  195. gl = GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
  196. }
  197. renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl);
  198. renderer.initialize();
  199. JmeSystem.setSoftTextDialogInput(this);
  200. needClose.set(false);
  201. }
  202. /**
  203. * De-initialize in the OpenGL thread.
  204. */
  205. protected void deinitInThread() {
  206. if (renderable.get()) {
  207. created.set(false);
  208. if (renderer != null) {
  209. renderer.cleanup();
  210. }
  211. listener.destroy();
  212. listener = null;
  213. renderer = null;
  214. timer = null;
  215. // do android specific cleaning here
  216. logger.fine("Display destroyed.");
  217. renderable.set(false);
  218. }
  219. }
  220. @Override
  221. public void setSettings(AppSettings settings) {
  222. this.settings.copyFrom(settings);
  223. if (androidInput != null) {
  224. androidInput.loadSettings(settings);
  225. }
  226. if (settings.getFrameRate() > 0) {
  227. minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms
  228. logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration);
  229. } else {
  230. minFrameDuration = 0;
  231. }
  232. }
  233. @Override
  234. public void setSystemListener(SystemListener listener) {
  235. this.listener = listener;
  236. }
  237. @Override
  238. public AppSettings getSettings() {
  239. return settings;
  240. }
  241. @Override
  242. public com.jme3.renderer.Renderer getRenderer() {
  243. return renderer;
  244. }
  245. @Override
  246. public MouseInput getMouseInput() {
  247. return new DummyMouseInput();
  248. }
  249. @Override
  250. public KeyInput getKeyInput() {
  251. return new DummyKeyInput();
  252. }
  253. @Override
  254. public JoyInput getJoyInput() {
  255. return androidInput.getJoyInput();
  256. }
  257. @Override
  258. public TouchInput getTouchInput() {
  259. return androidInput.getTouchInput();
  260. }
  261. @Override
  262. public Timer getTimer() {
  263. return timer;
  264. }
  265. @Override
  266. public void setTitle(String title) {
  267. }
  268. @Override
  269. public boolean isCreated() {
  270. return created.get();
  271. }
  272. @Override
  273. public void setAutoFlushFrames(boolean enabled) {
  274. this.autoFlush = enabled;
  275. }
  276. // SystemListener:reshape
  277. @Override
  278. public void onSurfaceChanged(GL10 gl, int width, int height) {
  279. logger.log(Level.FINE, "GL Surface changed, width: {0} height: {1}", new Object[]{width, height});
  280. // update the application settings with the new resolution
  281. settings.setResolution(width, height);
  282. // reload settings in androidInput so the correct touch event scaling can be
  283. // calculated in case the surface resolution is different than the view
  284. androidInput.loadSettings(settings);
  285. // if the application has already been initialized (ie renderable is set)
  286. // then call reshape so the app can adjust to the new resolution.
  287. if (renderable.get()) {
  288. logger.log(Level.FINE, "App already initialized, calling reshape");
  289. listener.reshape(width, height);
  290. }
  291. }
  292. // SystemListener:update
  293. @Override
  294. public void onDrawFrame(GL10 gl) {
  295. if (needClose.get()) {
  296. deinitInThread();
  297. return;
  298. }
  299. if (!renderable.get()) {
  300. if (created.get()) {
  301. logger.fine("GL Surface is setup, initializing application");
  302. listener.initialize();
  303. renderable.set(true);
  304. }
  305. } else {
  306. if (!created.get()) {
  307. throw new IllegalStateException("onDrawFrame without create");
  308. }
  309. listener.update();
  310. if (autoFlush) {
  311. renderer.postFrame();
  312. }
  313. long updateDelta = System.currentTimeMillis() - lastUpdateTime;
  314. // Enforce a FPS cap
  315. if (updateDelta < minFrameDuration) {
  316. // logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}",
  317. // new Object[]{lastUpdateTime, updateDelta, minTimePerFrame});
  318. try {
  319. Thread.sleep(minFrameDuration - updateDelta);
  320. } catch (InterruptedException e) {
  321. }
  322. }
  323. lastUpdateTime = System.currentTimeMillis();
  324. }
  325. }
  326. @Override
  327. public boolean isRenderable() {
  328. return renderable.get();
  329. }
  330. @Override
  331. public void create(boolean waitFor) {
  332. if (waitFor) {
  333. waitFor(true);
  334. }
  335. }
  336. public void create() {
  337. create(false);
  338. }
  339. @Override
  340. public void restart() {
  341. }
  342. @Override
  343. public void destroy(boolean waitFor) {
  344. needClose.set(true);
  345. if (waitFor) {
  346. waitFor(false);
  347. }
  348. }
  349. public void destroy() {
  350. destroy(true);
  351. }
  352. protected void waitFor(boolean createdVal) {
  353. while (renderable.get() != createdVal) {
  354. try {
  355. Thread.sleep(10);
  356. } catch (InterruptedException ex) {
  357. }
  358. }
  359. }
  360. public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) {
  361. logger.log(Level.FINE, "requestDialog: title: {0}, initialValue: {1}",
  362. new Object[]{title, initialValue});
  363. final View view = JmeAndroidSystem.getView();
  364. view.getHandler().post(new Runnable() {
  365. @Override
  366. public void run() {
  367. final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext());
  368. final EditText editTextDialogInput = new EditText(view.getContext());
  369. editTextDialogInput.setWidth(LayoutParams.FILL_PARENT);
  370. editTextDialogInput.setHeight(LayoutParams.FILL_PARENT);
  371. editTextDialogInput.setPadding(20, 20, 20, 20);
  372. editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL);
  373. //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
  374. editTextDialogInput.setText(initialValue);
  375. switch (id) {
  376. case SoftTextDialogInput.TEXT_ENTRY_DIALOG:
  377. editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT);
  378. break;
  379. case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG:
  380. editTextDialogInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
  381. break;
  382. case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG:
  383. editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE);
  384. break;
  385. default:
  386. break;
  387. }
  388. layoutTextDialogInput.addView(editTextDialogInput);
  389. AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK",
  390. new DialogInterface.OnClickListener() {
  391. public void onClick(DialogInterface dialog, int whichButton) {
  392. /* User clicked OK, send COMPLETE action
  393. * and text */
  394. listener.onSoftText(SoftTextDialogInputListener.COMPLETE, editTextDialogInput.getText().toString());
  395. }
  396. }).setNegativeButton("Cancel",
  397. new DialogInterface.OnClickListener() {
  398. public void onClick(DialogInterface dialog, int whichButton) {
  399. /* User clicked CANCEL, send CANCEL action
  400. * and text */
  401. listener.onSoftText(SoftTextDialogInputListener.CANCEL, editTextDialogInput.getText().toString());
  402. }
  403. }).create();
  404. dialogTextInput.show();
  405. }
  406. });
  407. }
  408. @Override
  409. public com.jme3.opencl.Context getOpenCLContext() {
  410. logger.warning("OpenCL is not yet supported on android");
  411. return null;
  412. }
  413. }