| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- /*
- * Copyright (c) 2009-2025 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.ActivityManager;
- import android.app.AlertDialog;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.content.pm.ConfigurationInfo;
- import android.graphics.PixelFormat;
- import android.graphics.Rect;
- import android.opengl.GLSurfaceView;
- import android.os.Build;
- import android.text.InputType;
- import android.view.Gravity;
- import android.view.SurfaceHolder;
- import android.view.SurfaceView;
- import android.view.View;
- import android.view.ViewGroup.LayoutParams;
- import android.widget.EditText;
- import android.widget.FrameLayout;
- import com.jme3.input.*;
- import com.jme3.input.android.AndroidInputHandler;
- import com.jme3.input.android.AndroidInputHandler14;
- import com.jme3.input.controls.SoftTextDialogInputListener;
- import com.jme3.input.dummy.DummyKeyInput;
- import com.jme3.input.dummy.DummyMouseInput;
- import com.jme3.renderer.android.AndroidGL;
- import com.jme3.renderer.opengl.*;
- import com.jme3.system.*;
- import com.jme3.util.BufferAllocatorFactory;
- import com.jme3.util.PrimitiveAllocator;
- import java.util.concurrent.atomic.AtomicBoolean;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javax.microedition.khronos.egl.EGLConfig;
- import javax.microedition.khronos.opengles.GL10;
- public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTextDialogInput {
- 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 AppSettings settings = new AppSettings(true);
- protected GLRenderer renderer;
- protected Timer timer;
- protected SystemListener listener;
- protected boolean autoFlush = true;
- protected AndroidInputHandler androidInput;
- protected long minFrameDuration = 0; // No FPS cap
- protected long lastUpdateTime = 0;
- static {
- final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;
- if (System.getProperty(implementation) == null) {
- System.setProperty(implementation, PrimitiveAllocator.class.getName());
- }
- }
- public OGLESContext() {}
- @Override
- public Type getType() {
- return Type.Display;
- }
- /**
- * <code>createView</code> creates the GLSurfaceView that the renderer will
- * draw to. <p> The result GLSurfaceView will receive input events and
- * forward them to the Application. Any rendering will be done into the
- * GLSurfaceView. Only one GLSurfaceView can be created at this time. The
- * given configType specifies how to determine the display configuration.
- *
- * @param context (not null)
- * @return GLSurfaceView The newly created view
- */
- public GLSurfaceView createView(Context context) {
- ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- ConfigurationInfo info = am.getDeviceConfigurationInfo();
- // NOTE: We assume all ICS devices have OpenGL ES 2.0.
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- // below 4.0, check OpenGL ES 2.0 support.
- if (info.reqGlEsVersion < 0x20000) {
- throw new UnsupportedOperationException(
- "OpenGL ES 2.0 or better is not supported on this device"
- );
- }
- } else if (Build.VERSION.SDK_INT < 9) {
- throw new UnsupportedOperationException("jME3 requires Android 2.3 or later");
- }
- // Start to set up the view
- GLSurfaceView view = new GLSurfaceView(context);
- logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
- if (androidInput == null) {
- if (Build.VERSION.SDK_INT >= 14) {
- androidInput = new AndroidInputHandler14();
- } else if (Build.VERSION.SDK_INT >= 9) {
- androidInput = new AndroidInputHandler();
- }
- }
- androidInput.setView(view);
- androidInput.loadSettings(settings);
- // setEGLContextClientVersion must be set before calling setRenderer
- // this means it cannot be set in AndroidConfigChooser (too late)
- // use proper openGL ES version
- view.setEGLContextClientVersion(info.reqGlEsVersion >> 16);
- view.setFocusableInTouchMode(true);
- view.setFocusable(true);
- // setFormat must be set before AndroidConfigChooser is called by the surfaceview.
- // if setFormat is called after ConfigChooser is called, then execution
- // stops at the setFormat call without a crash.
- // We look at the user setting for alpha bits and set the surfaceview
- // PixelFormat to either Opaque, Transparent, or Translucent.
- // ConfigChooser will do its best to honor the alpha requested by the user
- // For best rendering performance, use Opaque (alpha bits = 0).
- int curAlphaBits = settings.getAlphaBits();
- logger.log(Level.FINE, "curAlphaBits: {0}", curAlphaBits);
- if (curAlphaBits >= 8) {
- logger.log(Level.FINE, "Pixel Format: TRANSLUCENT");
- view.getHolder().setFormat(PixelFormat.TRANSLUCENT);
- view.setZOrderOnTop(true);
- } else if (curAlphaBits >= 1) {
- logger.log(Level.FINE, "Pixel Format: TRANSPARENT");
- view.getHolder().setFormat(PixelFormat.TRANSPARENT);
- } else {
- logger.log(Level.FINE, "Pixel Format: OPAQUE");
- view.getHolder().setFormat(PixelFormat.OPAQUE);
- }
- AndroidConfigChooser configChooser = new AndroidConfigChooser(settings);
- view.setEGLConfigChooser(configChooser);
- view.setRenderer(this);
- // Attempt to preserve the EGL Context on app pause/resume.
- // Not destroying and recreating the EGL context
- // will help with resume time by reusing the existing context to avoid
- // reloading all the OpenGL objects.
- if (Build.VERSION.SDK_INT >= 11) {
- view.setPreserveEGLContextOnPause(true);
- }
- return view;
- }
- // renderer:initialize
- @Override
- public void onSurfaceCreated(GL10 gl, EGLConfig cfg) {
- if (created.get() && renderer != null) {
- renderer.resetGLObjects();
- } else {
- if (!created.get()) {
- logger.fine("GL Surface created, initializing JME3 renderer");
- initInThread();
- } else {
- logger.warning("GL Surface already created");
- }
- }
- }
- protected void initInThread() {
- created.set(true);
- logger.fine("OGLESContext create");
- logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
- // Setup unhandled Exception Handler
- Thread
- .currentThread()
- .setUncaughtExceptionHandler(
- new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(Thread thread, Throwable thrown) {
- listener.handleError("Exception thrown in " + thread.toString(), thrown);
- }
- }
- );
- timer = new NanoTimer();
- GL gl = new AndroidGL();
- if (settings.getBoolean("GraphicsDebug")) {
- gl =
- (GL) GLDebug.createProxy(
- gl,
- gl,
- GL.class,
- GL2.class,
- GLES_30.class,
- GLFbo.class,
- GLExt.class
- );
- }
- if (settings.getBoolean("GraphicsTrace")) {
- gl = (GL) GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
- }
- renderer = new GLRenderer(gl, (GLExt) gl, (GLFbo) gl);
- renderer.initialize();
- JmeSystem.setSoftTextDialogInput(this);
- needClose.set(false);
- }
- /**
- * De-initialize in the OpenGL thread.
- */
- protected void deinitInThread() {
- if (renderable.get()) {
- created.set(false);
- if (renderer != null) {
- renderer.cleanup();
- }
- listener.destroy();
- // releases the view holder from the Android Input Resources
- // releasing the view enables the context instance to be
- // reclaimed by the GC.
- // if not released; it leads to a weak reference leak
- // disabling the destruction of the Context View Holder.
- androidInput.setView(null);
- // nullifying the references
- // signals their memory to be reclaimed
- listener = null;
- renderer = null;
- timer = null;
- androidInput = null;
- // do android specific cleaning here
- logger.fine("Display destroyed.");
- renderable.set(false);
- }
- }
- @Override
- public void setSettings(AppSettings settings) {
- this.settings.copyFrom(settings);
- if (androidInput != null) {
- androidInput.loadSettings(settings);
- }
- if (settings.getFrameRate() > 0) {
- minFrameDuration = (long) (1000d / settings.getFrameRate()); // ms
- logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration);
- } else {
- minFrameDuration = 0;
- }
- }
- /**
- * Accesses the listener that receives events related to this context.
- *
- * @return the pre-existing instance
- */
- @Override
- public SystemListener getSystemListener() {
- return listener;
- }
- @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 androidInput.getJoyInput();
- }
- @Override
- public TouchInput getTouchInput() {
- return androidInput.getTouchInput();
- }
- @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) {
- if (logger.isLoggable(Level.FINE)) {
- logger.log(
- Level.FINE,
- "GL Surface changed, width: {0} height: {1}",
- new Object[] { width, height }
- );
- }
- // update the application settings with the new resolution
- settings.setResolution(width, height);
- // Reload settings in androidInput so the correct touch event scaling can be
- // calculated in case the surface resolution is different than the view.
- androidInput.loadSettings(settings);
- // if the application has already been initialized (ie renderable is set)
- // then call reshape so the app can adjust to the new resolution.
- if (renderable.get()) {
- logger.log(Level.FINE, "App already initialized, calling reshape");
- listener.reshape(width, height);
- }
- }
- // SystemListener:update
- @Override
- public void onDrawFrame(GL10 gl) {
- if (needClose.get()) {
- deinitInThread();
- return;
- }
- if (!renderable.get()) {
- if (created.get()) {
- logger.fine("GL Surface is setup, initializing application");
- listener.initialize();
- renderable.set(true);
- }
- } else {
- if (!created.get()) {
- throw new IllegalStateException("onDrawFrame without create");
- }
- listener.update();
- if (autoFlush) {
- renderer.postFrame();
- }
- long updateDelta = System.currentTimeMillis() - lastUpdateTime;
- // Enforce a FPS cap
- if (updateDelta < minFrameDuration) {
- // logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}",
- // new Object[]{lastUpdateTime, updateDelta, minTimePerFrame});
- try {
- Thread.sleep(minFrameDuration - updateDelta);
- } catch (InterruptedException e) {}
- }
- lastUpdateTime = System.currentTimeMillis();
- }
- }
- @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) {}
- }
- }
- @Override
- public void requestDialog(
- final int id,
- final String title,
- final String initialValue,
- final SoftTextDialogInputListener listener
- ) {
- if (logger.isLoggable(Level.FINE)) {
- logger.log(
- Level.FINE,
- "requestDialog: title: {0}, initialValue: {1}",
- new Object[] { title, initialValue }
- );
- }
- final View view = JmeAndroidSystem.getView();
- view
- .getHandler()
- .post(
- new Runnable() {
- @Override
- public void run() {
- final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext());
- final EditText editTextDialogInput = new EditText(view.getContext());
- editTextDialogInput.setWidth(LayoutParams.FILL_PARENT);
- editTextDialogInput.setHeight(LayoutParams.FILL_PARENT);
- editTextDialogInput.setPadding(20, 20, 20, 20);
- editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL);
- //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
- editTextDialogInput.setText(initialValue);
- switch (id) {
- case SoftTextDialogInput.TEXT_ENTRY_DIALOG:
- editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT);
- break;
- case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG:
- editTextDialogInput.setInputType(
- InputType.TYPE_CLASS_NUMBER |
- InputType.TYPE_NUMBER_FLAG_DECIMAL |
- InputType.TYPE_NUMBER_FLAG_SIGNED
- );
- break;
- case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG:
- editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE);
- break;
- default:
- break;
- }
- layoutTextDialogInput.addView(editTextDialogInput);
- AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext())
- .setTitle(title)
- .setView(layoutTextDialogInput)
- .setPositiveButton(
- "OK",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- /* User clicked OK, send COMPLETE action
- * and text */
- listener.onSoftText(
- SoftTextDialogInputListener.COMPLETE,
- editTextDialogInput.getText().toString()
- );
- }
- }
- )
- .setNegativeButton(
- "Cancel",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- /* User clicked CANCEL, send CANCEL action
- * and text */
- listener.onSoftText(
- SoftTextDialogInputListener.CANCEL,
- editTextDialogInput.getText().toString()
- );
- }
- }
- )
- .create();
- dialogTextInput.show();
- }
- }
- );
- }
- @Override
- public com.jme3.opencl.Context getOpenCLContext() {
- logger.warning("OpenCL is not yet supported on android");
- return null;
- }
- /**
- * Returns the height of the input surface.
- *
- * @return the height (in pixels)
- */
- @Override
- public int getFramebufferHeight() {
- Rect rect = getSurfaceFrame();
- int result = rect.height();
- return result;
- }
- /**
- * Returns the width of the input surface.
- *
- * @return the width (in pixels)
- */
- @Override
- public int getFramebufferWidth() {
- Rect rect = getSurfaceFrame();
- int result = rect.width();
- return result;
- }
- /**
- * Returns the screen X coordinate of the left edge of the content area.
- *
- * @throws UnsupportedOperationException
- */
- @Override
- public int getWindowXPosition() {
- throw new UnsupportedOperationException("not implemented yet");
- }
- /**
- * Returns the screen Y coordinate of the top edge of the content area.
- *
- * @throws UnsupportedOperationException
- */
- @Override
- public int getWindowYPosition() {
- throw new UnsupportedOperationException("not implemented yet");
- }
- /**
- * Retrieves the dimensions of the input surface. Note: do not modify the
- * returned object.
- *
- * @return the dimensions (in pixels, left and top are 0)
- */
- private Rect getSurfaceFrame() {
- SurfaceView view = (SurfaceView) androidInput.getView();
- SurfaceHolder holder = view.getHolder();
- Rect result = holder.getSurfaceFrame();
- return result;
- }
- @Override
- public Displays getDisplays() {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public int getPrimaryDisplay() {
- // TODO Auto-generated method stub
- return 0;
- }
- }
|