OGLESContext.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. /*
  2. * Copyright (c) 2009-2025 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.graphics.Rect;
  40. import android.opengl.GLSurfaceView;
  41. import android.os.Build;
  42. import android.text.InputType;
  43. import android.view.Gravity;
  44. import android.view.SurfaceHolder;
  45. import android.view.SurfaceView;
  46. import android.view.View;
  47. import android.view.ViewGroup.LayoutParams;
  48. import android.widget.EditText;
  49. import android.widget.FrameLayout;
  50. import com.jme3.input.*;
  51. import com.jme3.input.android.AndroidInputHandler;
  52. import com.jme3.input.android.AndroidInputHandler14;
  53. import com.jme3.input.controls.SoftTextDialogInputListener;
  54. import com.jme3.input.dummy.DummyKeyInput;
  55. import com.jme3.input.dummy.DummyMouseInput;
  56. import com.jme3.renderer.android.AndroidGL;
  57. import com.jme3.renderer.opengl.*;
  58. import com.jme3.system.*;
  59. import com.jme3.util.BufferAllocatorFactory;
  60. import com.jme3.util.PrimitiveAllocator;
  61. import java.util.concurrent.atomic.AtomicBoolean;
  62. import java.util.logging.Level;
  63. import java.util.logging.Logger;
  64. import javax.microedition.khronos.egl.EGLConfig;
  65. import javax.microedition.khronos.opengles.GL10;
  66. public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTextDialogInput {
  67. private static final Logger logger = Logger.getLogger(OGLESContext.class.getName());
  68. protected final AtomicBoolean created = new AtomicBoolean(false);
  69. protected final AtomicBoolean renderable = new AtomicBoolean(false);
  70. protected final AtomicBoolean needClose = new AtomicBoolean(false);
  71. protected AppSettings settings = new AppSettings(true);
  72. protected GLRenderer renderer;
  73. protected Timer timer;
  74. protected SystemListener listener;
  75. protected boolean autoFlush = true;
  76. protected AndroidInputHandler androidInput;
  77. protected long minFrameDuration = 0; // No FPS cap
  78. protected long lastUpdateTime = 0;
  79. static {
  80. final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;
  81. if (System.getProperty(implementation) == null) {
  82. System.setProperty(implementation, PrimitiveAllocator.class.getName());
  83. }
  84. }
  85. public OGLESContext() {}
  86. @Override
  87. public Type getType() {
  88. return Type.Display;
  89. }
  90. /**
  91. * <code>createView</code> creates the GLSurfaceView that the renderer will
  92. * draw to. <p> The result GLSurfaceView will receive input events and
  93. * forward them to the Application. Any rendering will be done into the
  94. * GLSurfaceView. Only one GLSurfaceView can be created at this time. The
  95. * given configType specifies how to determine the display configuration.
  96. *
  97. * @param context (not null)
  98. * @return GLSurfaceView The newly created view
  99. */
  100. public GLSurfaceView createView(Context context) {
  101. ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  102. ConfigurationInfo info = am.getDeviceConfigurationInfo();
  103. // NOTE: We assume all ICS devices have OpenGL ES 2.0.
  104. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  105. // below 4.0, check OpenGL ES 2.0 support.
  106. if (info.reqGlEsVersion < 0x20000) {
  107. throw new UnsupportedOperationException(
  108. "OpenGL ES 2.0 or better is not supported on this device"
  109. );
  110. }
  111. } else if (Build.VERSION.SDK_INT < 9) {
  112. throw new UnsupportedOperationException("jME3 requires Android 2.3 or later");
  113. }
  114. // Start to set up the view
  115. GLSurfaceView view = new GLSurfaceView(context);
  116. logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
  117. if (androidInput == null) {
  118. if (Build.VERSION.SDK_INT >= 14) {
  119. androidInput = new AndroidInputHandler14();
  120. } else if (Build.VERSION.SDK_INT >= 9) {
  121. androidInput = new AndroidInputHandler();
  122. }
  123. }
  124. androidInput.setView(view);
  125. androidInput.loadSettings(settings);
  126. // setEGLContextClientVersion must be set before calling setRenderer
  127. // this means it cannot be set in AndroidConfigChooser (too late)
  128. // use proper openGL ES version
  129. view.setEGLContextClientVersion(info.reqGlEsVersion >> 16);
  130. view.setFocusableInTouchMode(true);
  131. view.setFocusable(true);
  132. // setFormat must be set before AndroidConfigChooser is called by the surfaceview.
  133. // if setFormat is called after ConfigChooser is called, then execution
  134. // stops at the setFormat call without a crash.
  135. // We look at the user setting for alpha bits and set the surfaceview
  136. // PixelFormat to either Opaque, Transparent, or Translucent.
  137. // ConfigChooser will do its best to honor the alpha requested by the user
  138. // For best rendering performance, use Opaque (alpha bits = 0).
  139. int curAlphaBits = settings.getAlphaBits();
  140. logger.log(Level.FINE, "curAlphaBits: {0}", curAlphaBits);
  141. if (curAlphaBits >= 8) {
  142. logger.log(Level.FINE, "Pixel Format: TRANSLUCENT");
  143. view.getHolder().setFormat(PixelFormat.TRANSLUCENT);
  144. view.setZOrderOnTop(true);
  145. } else if (curAlphaBits >= 1) {
  146. logger.log(Level.FINE, "Pixel Format: TRANSPARENT");
  147. view.getHolder().setFormat(PixelFormat.TRANSPARENT);
  148. } else {
  149. logger.log(Level.FINE, "Pixel Format: OPAQUE");
  150. view.getHolder().setFormat(PixelFormat.OPAQUE);
  151. }
  152. AndroidConfigChooser configChooser = new AndroidConfigChooser(settings);
  153. view.setEGLConfigChooser(configChooser);
  154. view.setRenderer(this);
  155. // Attempt to preserve the EGL Context on app pause/resume.
  156. // Not destroying and recreating the EGL context
  157. // will help with resume time by reusing the existing context to avoid
  158. // reloading all the OpenGL objects.
  159. if (Build.VERSION.SDK_INT >= 11) {
  160. view.setPreserveEGLContextOnPause(true);
  161. }
  162. return view;
  163. }
  164. // renderer:initialize
  165. @Override
  166. public void onSurfaceCreated(GL10 gl, EGLConfig cfg) {
  167. if (created.get() && renderer != null) {
  168. renderer.resetGLObjects();
  169. } else {
  170. if (!created.get()) {
  171. logger.fine("GL Surface created, initializing JME3 renderer");
  172. initInThread();
  173. } else {
  174. logger.warning("GL Surface already created");
  175. }
  176. }
  177. }
  178. protected void initInThread() {
  179. created.set(true);
  180. logger.fine("OGLESContext create");
  181. logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
  182. // Setup unhandled Exception Handler
  183. Thread
  184. .currentThread()
  185. .setUncaughtExceptionHandler(
  186. new Thread.UncaughtExceptionHandler() {
  187. @Override
  188. public void uncaughtException(Thread thread, Throwable thrown) {
  189. listener.handleError("Exception thrown in " + thread.toString(), thrown);
  190. }
  191. }
  192. );
  193. timer = new NanoTimer();
  194. GL gl = new AndroidGL();
  195. if (settings.getBoolean("GraphicsDebug")) {
  196. gl =
  197. (GL) GLDebug.createProxy(
  198. gl,
  199. gl,
  200. GL.class,
  201. GL2.class,
  202. GLES_30.class,
  203. GLFbo.class,
  204. GLExt.class
  205. );
  206. }
  207. if (settings.getBoolean("GraphicsTrace")) {
  208. gl = (GL) GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
  209. }
  210. renderer = new GLRenderer(gl, (GLExt) gl, (GLFbo) gl);
  211. renderer.initialize();
  212. JmeSystem.setSoftTextDialogInput(this);
  213. needClose.set(false);
  214. }
  215. /**
  216. * De-initialize in the OpenGL thread.
  217. */
  218. protected void deinitInThread() {
  219. if (renderable.get()) {
  220. created.set(false);
  221. if (renderer != null) {
  222. renderer.cleanup();
  223. }
  224. listener.destroy();
  225. // releases the view holder from the Android Input Resources
  226. // releasing the view enables the context instance to be
  227. // reclaimed by the GC.
  228. // if not released; it leads to a weak reference leak
  229. // disabling the destruction of the Context View Holder.
  230. androidInput.setView(null);
  231. // nullifying the references
  232. // signals their memory to be reclaimed
  233. listener = null;
  234. renderer = null;
  235. timer = null;
  236. androidInput = null;
  237. // do android specific cleaning here
  238. logger.fine("Display destroyed.");
  239. renderable.set(false);
  240. }
  241. }
  242. @Override
  243. public void setSettings(AppSettings settings) {
  244. this.settings.copyFrom(settings);
  245. if (androidInput != null) {
  246. androidInput.loadSettings(settings);
  247. }
  248. if (settings.getFrameRate() > 0) {
  249. minFrameDuration = (long) (1000d / settings.getFrameRate()); // ms
  250. logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration);
  251. } else {
  252. minFrameDuration = 0;
  253. }
  254. }
  255. /**
  256. * Accesses the listener that receives events related to this context.
  257. *
  258. * @return the pre-existing instance
  259. */
  260. @Override
  261. public SystemListener getSystemListener() {
  262. return listener;
  263. }
  264. @Override
  265. public void setSystemListener(SystemListener listener) {
  266. this.listener = listener;
  267. }
  268. @Override
  269. public AppSettings getSettings() {
  270. return settings;
  271. }
  272. @Override
  273. public com.jme3.renderer.Renderer getRenderer() {
  274. return renderer;
  275. }
  276. @Override
  277. public MouseInput getMouseInput() {
  278. return new DummyMouseInput();
  279. }
  280. @Override
  281. public KeyInput getKeyInput() {
  282. return new DummyKeyInput();
  283. }
  284. @Override
  285. public JoyInput getJoyInput() {
  286. return androidInput.getJoyInput();
  287. }
  288. @Override
  289. public TouchInput getTouchInput() {
  290. return androidInput.getTouchInput();
  291. }
  292. @Override
  293. public Timer getTimer() {
  294. return timer;
  295. }
  296. @Override
  297. public void setTitle(String title) {}
  298. @Override
  299. public boolean isCreated() {
  300. return created.get();
  301. }
  302. @Override
  303. public void setAutoFlushFrames(boolean enabled) {
  304. this.autoFlush = enabled;
  305. }
  306. // SystemListener:reshape
  307. @Override
  308. public void onSurfaceChanged(GL10 gl, int width, int height) {
  309. if (logger.isLoggable(Level.FINE)) {
  310. logger.log(
  311. Level.FINE,
  312. "GL Surface changed, width: {0} height: {1}",
  313. new Object[] { width, height }
  314. );
  315. }
  316. // update the application settings with the new resolution
  317. settings.setResolution(width, height);
  318. // Reload settings in androidInput so the correct touch event scaling can be
  319. // calculated in case the surface resolution is different than the view.
  320. androidInput.loadSettings(settings);
  321. // if the application has already been initialized (ie renderable is set)
  322. // then call reshape so the app can adjust to the new resolution.
  323. if (renderable.get()) {
  324. logger.log(Level.FINE, "App already initialized, calling reshape");
  325. listener.reshape(width, height);
  326. }
  327. }
  328. // SystemListener:update
  329. @Override
  330. public void onDrawFrame(GL10 gl) {
  331. if (needClose.get()) {
  332. deinitInThread();
  333. return;
  334. }
  335. if (!renderable.get()) {
  336. if (created.get()) {
  337. logger.fine("GL Surface is setup, initializing application");
  338. listener.initialize();
  339. renderable.set(true);
  340. }
  341. } else {
  342. if (!created.get()) {
  343. throw new IllegalStateException("onDrawFrame without create");
  344. }
  345. listener.update();
  346. if (autoFlush) {
  347. renderer.postFrame();
  348. }
  349. long updateDelta = System.currentTimeMillis() - lastUpdateTime;
  350. // Enforce a FPS cap
  351. if (updateDelta < minFrameDuration) {
  352. // logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}",
  353. // new Object[]{lastUpdateTime, updateDelta, minTimePerFrame});
  354. try {
  355. Thread.sleep(minFrameDuration - updateDelta);
  356. } catch (InterruptedException e) {}
  357. }
  358. lastUpdateTime = System.currentTimeMillis();
  359. }
  360. }
  361. @Override
  362. public boolean isRenderable() {
  363. return renderable.get();
  364. }
  365. @Override
  366. public void create(boolean waitFor) {
  367. if (waitFor) {
  368. waitFor(true);
  369. }
  370. }
  371. public void create() {
  372. create(false);
  373. }
  374. @Override
  375. public void restart() {}
  376. @Override
  377. public void destroy(boolean waitFor) {
  378. needClose.set(true);
  379. if (waitFor) {
  380. waitFor(false);
  381. }
  382. }
  383. public void destroy() {
  384. destroy(true);
  385. }
  386. protected void waitFor(boolean createdVal) {
  387. while (renderable.get() != createdVal) {
  388. try {
  389. Thread.sleep(10);
  390. } catch (InterruptedException ex) {}
  391. }
  392. }
  393. @Override
  394. public void requestDialog(
  395. final int id,
  396. final String title,
  397. final String initialValue,
  398. final SoftTextDialogInputListener listener
  399. ) {
  400. if (logger.isLoggable(Level.FINE)) {
  401. logger.log(
  402. Level.FINE,
  403. "requestDialog: title: {0}, initialValue: {1}",
  404. new Object[] { title, initialValue }
  405. );
  406. }
  407. final View view = JmeAndroidSystem.getView();
  408. view
  409. .getHandler()
  410. .post(
  411. new Runnable() {
  412. @Override
  413. public void run() {
  414. final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext());
  415. final EditText editTextDialogInput = new EditText(view.getContext());
  416. editTextDialogInput.setWidth(LayoutParams.FILL_PARENT);
  417. editTextDialogInput.setHeight(LayoutParams.FILL_PARENT);
  418. editTextDialogInput.setPadding(20, 20, 20, 20);
  419. editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL);
  420. //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
  421. editTextDialogInput.setText(initialValue);
  422. switch (id) {
  423. case SoftTextDialogInput.TEXT_ENTRY_DIALOG:
  424. editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT);
  425. break;
  426. case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG:
  427. editTextDialogInput.setInputType(
  428. InputType.TYPE_CLASS_NUMBER |
  429. InputType.TYPE_NUMBER_FLAG_DECIMAL |
  430. InputType.TYPE_NUMBER_FLAG_SIGNED
  431. );
  432. break;
  433. case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG:
  434. editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE);
  435. break;
  436. default:
  437. break;
  438. }
  439. layoutTextDialogInput.addView(editTextDialogInput);
  440. AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext())
  441. .setTitle(title)
  442. .setView(layoutTextDialogInput)
  443. .setPositiveButton(
  444. "OK",
  445. new DialogInterface.OnClickListener() {
  446. @Override
  447. public void onClick(DialogInterface dialog, int whichButton) {
  448. /* User clicked OK, send COMPLETE action
  449. * and text */
  450. listener.onSoftText(
  451. SoftTextDialogInputListener.COMPLETE,
  452. editTextDialogInput.getText().toString()
  453. );
  454. }
  455. }
  456. )
  457. .setNegativeButton(
  458. "Cancel",
  459. new DialogInterface.OnClickListener() {
  460. @Override
  461. public void onClick(DialogInterface dialog, int whichButton) {
  462. /* User clicked CANCEL, send CANCEL action
  463. * and text */
  464. listener.onSoftText(
  465. SoftTextDialogInputListener.CANCEL,
  466. editTextDialogInput.getText().toString()
  467. );
  468. }
  469. }
  470. )
  471. .create();
  472. dialogTextInput.show();
  473. }
  474. }
  475. );
  476. }
  477. @Override
  478. public com.jme3.opencl.Context getOpenCLContext() {
  479. logger.warning("OpenCL is not yet supported on android");
  480. return null;
  481. }
  482. /**
  483. * Returns the height of the input surface.
  484. *
  485. * @return the height (in pixels)
  486. */
  487. @Override
  488. public int getFramebufferHeight() {
  489. Rect rect = getSurfaceFrame();
  490. int result = rect.height();
  491. return result;
  492. }
  493. /**
  494. * Returns the width of the input surface.
  495. *
  496. * @return the width (in pixels)
  497. */
  498. @Override
  499. public int getFramebufferWidth() {
  500. Rect rect = getSurfaceFrame();
  501. int result = rect.width();
  502. return result;
  503. }
  504. /**
  505. * Returns the screen X coordinate of the left edge of the content area.
  506. *
  507. * @throws UnsupportedOperationException
  508. */
  509. @Override
  510. public int getWindowXPosition() {
  511. throw new UnsupportedOperationException("not implemented yet");
  512. }
  513. /**
  514. * Returns the screen Y coordinate of the top edge of the content area.
  515. *
  516. * @throws UnsupportedOperationException
  517. */
  518. @Override
  519. public int getWindowYPosition() {
  520. throw new UnsupportedOperationException("not implemented yet");
  521. }
  522. /**
  523. * Retrieves the dimensions of the input surface. Note: do not modify the
  524. * returned object.
  525. *
  526. * @return the dimensions (in pixels, left and top are 0)
  527. */
  528. private Rect getSurfaceFrame() {
  529. SurfaceView view = (SurfaceView) androidInput.getView();
  530. SurfaceHolder holder = view.getHolder();
  531. Rect result = holder.getSurfaceFrame();
  532. return result;
  533. }
  534. @Override
  535. public Displays getDisplays() {
  536. // TODO Auto-generated method stub
  537. return null;
  538. }
  539. @Override
  540. public int getPrimaryDisplay() {
  541. // TODO Auto-generated method stub
  542. return 0;
  543. }
  544. }