diff options
author | Scott Barta <sbarta@google.com> | 2012-03-01 12:35:35 -0800 |
---|---|---|
committer | Scott Barta <sbarta@google.com> | 2012-03-01 12:40:08 -0800 |
commit | 59b2e6871c65f58fdad78cd7229c292f6a177578 (patch) | |
tree | 2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/android/com/jme3 | |
parent | f9b30489e75ac1eabc365064959804e99534f7ab (diff) | |
download | jmonkeyengine-59b2e6871c65f58fdad78cd7229c292f6a177578.tar.gz |
Adds the jMonkeyEngine library to the build.
Adds the jMonkeyEngine open source 3D game engine to the build. This
is built as a static library and is only used by the Finsky client.
Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
Diffstat (limited to 'engine/src/android/com/jme3')
21 files changed, 6731 insertions, 0 deletions
diff --git a/engine/src/android/com/jme3/app/AndroidHarness.java b/engine/src/android/com/jme3/app/AndroidHarness.java new file mode 100644 index 0000000..6631003 --- /dev/null +++ b/engine/src/android/com/jme3/app/AndroidHarness.java @@ -0,0 +1,398 @@ +package com.jme3.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.pm.ActivityInfo;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.*;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.android.AndroidAudioRenderer;
+import com.jme3.input.android.AndroidInput;
+import com.jme3.input.controls.TouchListener;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeSystem;
+import com.jme3.system.android.AndroidConfigChooser.ConfigType;
+import com.jme3.system.android.JmeAndroidSystem;
+import com.jme3.system.android.OGLESContext;
+import com.jme3.util.JmeFormatter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidHarness</code> wraps a jme application object and runs it on
+ * Android
+ *
+ * @author Kirill
+ * @author larynx
+ */
+public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener {
+
+ protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
+ /**
+ * The application class to start
+ */
+ protected String appClass = "jme3test.android.Test";
+ /**
+ * The jme3 application object
+ */
+ protected Application app = null;
+ /**
+ * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is
+ * RGBA8888 or better if supported by the hardware
+ */
+ protected ConfigType eglConfigType = ConfigType.FASTEST;
+ /**
+ * If true all valid and not valid egl configs are logged
+ */
+ protected boolean eglConfigVerboseLogging = false;
+ /**
+ * If true MouseEvents are generated from TouchEvents
+ */
+ protected boolean mouseEventsEnabled = true;
+ /**
+ * Flip X axis
+ */
+ protected boolean mouseEventsInvertX = true;
+ /**
+ * Flip Y axis
+ */
+ protected boolean mouseEventsInvertY = true;
+ /**
+ * Title of the exit dialog, default is "Do you want to exit?"
+ */
+ protected String exitDialogTitle = "Do you want to exit?";
+ /**
+ * Message of the exit dialog, default is "Use your home key to bring this
+ * app into the background or exit to terminate it."
+ */
+ protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
+ /**
+ * Set the screen window mode. If screenFullSize is true, then the
+ * notification bar and title bar are removed and the screen covers the
+ * entire display. If screenFullSize is false, then the notification bar
+ * remains visible if screenShowTitle is true while screenFullScreen is
+ * false, then the title bar is also displayed under the notification bar.
+ */
+ protected boolean screenFullScreen = true;
+ /**
+ * if screenShowTitle is true while screenFullScreen is false, then the
+ * title bar is also displayed under the notification bar
+ */
+ protected boolean screenShowTitle = true;
+ /**
+ * Splash Screen picture Resource ID. If a Splash Screen is desired, set
+ * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
+ * splashPicID = 0, then no splash screen will be displayed.
+ */
+ protected int splashPicID = 0;
+ /**
+ * Set the screen orientation, default is SENSOR
+ * ActivityInfo.SCREEN_ORIENTATION_* constants package
+ * android.content.pm.ActivityInfo
+ *
+ * SCREEN_ORIENTATION_UNSPECIFIED SCREEN_ORIENTATION_LANDSCAPE
+ * SCREEN_ORIENTATION_PORTRAIT SCREEN_ORIENTATION_USER
+ * SCREEN_ORIENTATION_BEHIND SCREEN_ORIENTATION_SENSOR (default)
+ * SCREEN_ORIENTATION_NOSENSOR
+ */
+ protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
+ protected OGLESContext ctx;
+ protected GLSurfaceView view = null;
+ protected boolean isGLThreadPaused = true;
+ private ImageView splashImageView = null;
+ private FrameLayout frameLayout = null;
+ final private String ESCAPE_EVENT = "TouchEscape";
+
+ static {
+ try {
+ System.loadLibrary("bulletjme");
+ } catch (UnsatisfiedLinkError e) {
+ }
+ JmeSystem.setSystemDelegate(new JmeAndroidSystem());
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Logger log = logger;
+ boolean bIsLogFormatSet = false;
+ do {
+ if (log.getHandlers().length == 0) {
+ log = logger.getParent();
+ if (log != null) {
+ for (Handler h : log.getHandlers()) {
+ //h.setFormatter(new SimpleFormatter());
+ h.setFormatter(new JmeFormatter());
+ bIsLogFormatSet = true;
+ }
+ }
+ }
+ } while (log != null && !bIsLogFormatSet);
+
+ JmeAndroidSystem.setResources(getResources());
+ JmeAndroidSystem.setActivity(this);
+
+ if (screenFullScreen) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ } else {
+ if (!screenShowTitle) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ }
+ }
+
+ setRequestedOrientation(screenOrientation);
+
+ // Create Settings
+ AppSettings settings = new AppSettings(true);
+
+ // Create the input class
+ AndroidInput input = new AndroidInput(this);
+ input.setMouseEventsInvertX(mouseEventsInvertX);
+ input.setMouseEventsInvertY(mouseEventsInvertY);
+ input.setMouseEventsEnabled(mouseEventsEnabled);
+
+ // Create application instance
+ try {
+ if (app == null) {
+ @SuppressWarnings("unchecked")
+ Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
+ app = clazz.newInstance();
+ }
+
+ app.setSettings(settings);
+ app.start();
+ ctx = (OGLESContext) app.getContext();
+ view = ctx.createView(input, eglConfigType, eglConfigVerboseLogging);
+
+ // Set the screen reolution
+ WindowManager wind = this.getWindowManager();
+ Display disp = wind.getDefaultDisplay();
+ ctx.getSettings().setResolution(disp.getWidth(), disp.getHeight());
+
+ AppSettings s = ctx.getSettings();
+ logger.log(Level.INFO, "Settings: Width {0} Height {1}", new Object[]{s.getWidth(), s.getHeight()});
+
+ layoutDisplay();
+ } catch (Exception ex) {
+ handleError("Class " + appClass + " init failed", ex);
+ setContentView(new TextView(this));
+ }
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ if (app != null) {
+ app.restart();
+ }
+
+ logger.info("onRestart");
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ logger.info("onStart");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (view != null) {
+ view.onResume();
+ }
+
+ //resume the audio
+ AudioRenderer result = app.getAudioRenderer();
+ if (result != null) {
+ if (result instanceof AndroidAudioRenderer) {
+ AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
+ renderer.resumeAll();
+ }
+ }
+
+ isGLThreadPaused = false;
+ logger.info("onResume");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (view != null) {
+ view.onPause();
+ }
+
+ //pause the audio
+ AudioRenderer result = app.getAudioRenderer();
+ if (result != null) {
+ logger.info("pause: " + result.getClass().getSimpleName());
+ if (result instanceof AndroidAudioRenderer) {
+ AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
+ renderer.pauseAll();
+ }
+ }
+
+ isGLThreadPaused = true;
+ logger.info("onPause");
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ logger.info("onStop");
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (app != null) {
+ app.stop(!isGLThreadPaused);
+ }
+
+ logger.info("onDestroy");
+ super.onDestroy();
+ }
+
+ public Application getJmeApplication() {
+ return app;
+ }
+
+ /**
+ * Called when an error has occurred. By default, will show an error message
+ * to the user and print the exception/error to the log.
+ */
+ public void handleError(final String errorMsg, final Throwable t) {
+ String stackTrace = "";
+ String title = "Error";
+
+ if (t != null) {
+ // Convert exception to string
+ StringWriter sw = new StringWriter(100);
+ t.printStackTrace(new PrintWriter(sw));
+ stackTrace = sw.toString();
+ title = t.toString();
+ }
+
+ final String finalTitle = title;
+ final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
+ + "\n" + stackTrace;
+
+ logger.log(Level.SEVERE, finalMsg);
+
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create();
+ dialog.show();
+ }
+ });
+ }
+
+ /**
+ * Called by the android alert dialog, terminate the activity and OpenGL
+ * rendering
+ *
+ * @param dialog
+ * @param whichButton
+ */
+ public void onClick(DialogInterface dialog, int whichButton) {
+ if (whichButton != -2) {
+ if (app != null) {
+ app.stop(true);
+ }
+ this.finish();
+ }
+ }
+
+ /**
+ * Gets called by the InputManager on all touch/drag/scale events
+ */
+ @Override
+ public void onTouch(String name, TouchEvent evt, float tpf) {
+ if (name.equals(ESCAPE_EVENT)) {
+ switch (evt.getType()) {
+ case KEY_UP:
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create();
+ dialog.show();
+ }
+ });
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public void layoutDisplay() {
+ logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);
+ if (splashPicID != 0) {
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ LayoutParams.FILL_PARENT,
+ LayoutParams.FILL_PARENT,
+ Gravity.CENTER);
+
+ frameLayout = new FrameLayout(this);
+ splashImageView = new ImageView(this);
+
+ Drawable drawable = this.getResources().getDrawable(splashPicID);
+ if (drawable instanceof NinePatchDrawable) {
+ splashImageView.setBackgroundDrawable(drawable);
+ } else {
+ splashImageView.setImageResource(splashPicID);
+ }
+
+ frameLayout.addView(view);
+ frameLayout.addView(splashImageView, lp);
+
+ setContentView(frameLayout);
+ logger.log(Level.INFO, "Splash Screen Created");
+ } else {
+ logger.log(Level.INFO, "Splash Screen Skipped.");
+ setContentView(view);
+ }
+ }
+
+ public void removeSplashScreen() {
+ logger.log(Level.INFO, "Splash Screen Picture Resource ID: {0}", splashPicID);
+ if (splashPicID != 0) {
+ if (frameLayout != null) {
+ if (splashImageView != null) {
+ this.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ splashImageView.setVisibility(View.INVISIBLE);
+ frameLayout.removeView(splashImageView);
+ }
+ });
+ } else {
+ logger.log(Level.INFO, "splashImageView is null");
+ }
+ } else {
+ logger.log(Level.INFO, "frameLayout is null");
+ }
+ }
+ }
+}
diff --git a/engine/src/android/com/jme3/app/R.java b/engine/src/android/com/jme3/app/R.java new file mode 100644 index 0000000..4db44e3 --- /dev/null +++ b/engine/src/android/com/jme3/app/R.java @@ -0,0 +1,20 @@ +/* AUTO-GENERATED FILE. DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found. It
+ * should not be modified by hand.
+ */
+
+package com.jme3.app;
+
+public final class R {
+ public static final class attr {
+ }
+ public static final class layout {
+ public static final int main=0x7f020000;
+ }
+ public static final class string {
+ public static final int app_name=0x7f030000;
+ public static final int jme3_appclass=0x7f030001;
+ }
+}
diff --git a/engine/src/android/com/jme3/asset/AndroidAssetManager.java b/engine/src/android/com/jme3/asset/AndroidAssetManager.java new file mode 100644 index 0000000..c6f0b17 --- /dev/null +++ b/engine/src/android/com/jme3/asset/AndroidAssetManager.java @@ -0,0 +1,115 @@ +/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.asset.plugins.AndroidLocator;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.audio.plugins.AndroidAudioLoader;
+import com.jme3.texture.Texture;
+import com.jme3.texture.plugins.AndroidImageLoader;
+import java.net.URL;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidAssetManager</code> is an implementation of DesktopAssetManager for Android
+ *
+ * @author larynx
+ */
+public class AndroidAssetManager extends DesktopAssetManager {
+
+ private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName());
+
+ public AndroidAssetManager() {
+ this(null);
+ }
+
+ @Deprecated
+ public AndroidAssetManager(boolean loadDefaults) {
+ //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"));
+ this(null);
+ }
+
+ /**
+ * AndroidAssetManager constructor
+ * If URL == null then a default list of locators and loaders for android is set
+ * @param configFile
+ */
+ public AndroidAssetManager(URL configFile) {
+ System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
+
+ // Set Default Android config
+ this.registerLocator("", AndroidLocator.class);
+ this.registerLocator("", ClasspathLocator.class);
+ this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg");
+ this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav");
+ this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m");
+ this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md");
+ this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt");
+ this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds");
+ this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm");
+ this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr");
+ this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga");
+ this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o");
+ this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj");
+ this.registerLoader(com.jme3.scene.plugins.MTLLoader.class, "mtl");
+ this.registerLoader(com.jme3.scene.plugins.ogre.MeshLoader.class, "meshxml", "mesh.xml");
+ this.registerLoader(com.jme3.scene.plugins.ogre.SkeletonLoader.class, "skeletonxml", "skeleton.xml");
+ this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material");
+ this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene");
+ this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib");
+
+ logger.info("AndroidAssetManager created.");
+ }
+
+ /**
+ * Loads a texture.
+ *
+ * @return
+ */
+ @Override
+ public Texture loadTexture(TextureKey key) {
+ Texture tex = (Texture) loadAsset(key);
+
+ // XXX: This will improve performance on some really
+ // low end GPUs (e.g. ones with OpenGL ES 1 support only)
+ // but otherwise won't help on the higher ones.
+ // Strongly consider removing this.
+ tex.setMagFilter(Texture.MagFilter.Nearest);
+ tex.setAnisotropicFilter(0);
+ if (tex.getMinFilter().usesMipMapLevels()) {
+ tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap);
+ } else {
+ tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ }
+ return tex;
+ }
+}
diff --git a/engine/src/android/com/jme3/asset/AndroidImageInfo.java b/engine/src/android/com/jme3/asset/AndroidImageInfo.java new file mode 100644 index 0000000..c8eb539 --- /dev/null +++ b/engine/src/android/com/jme3/asset/AndroidImageInfo.java @@ -0,0 +1,101 @@ +package com.jme3.asset; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.io.IOException; +import java.io.InputStream; + +/** + * <code>AndroidImageInfo</code> is set in a jME3 image via the {@link Image#setEfficientData(java.lang.Object)} + * method to retrieve a {@link Bitmap} when it is needed by the renderer. + * User code may extend <code>AndroidImageInfo</code> and provide their own implementation of the + * {@link AndroidImageInfo#loadBitmap()} method to acquire a bitmap by their own means. + * + * @author Kirill Vainer + */ +public class AndroidImageInfo { + + protected AssetInfo assetInfo; + protected Bitmap bitmap; + protected Format format; + + public AndroidImageInfo(AssetInfo assetInfo) { + this.assetInfo = assetInfo; + } + + public Bitmap getBitmap(){ + if (bitmap == null || bitmap.isRecycled()){ + try { + loadBitmap(); + } catch (IOException ex) { + // If called first inside AssetManager, the error will propagate + // correctly. Assuming that if the first calls succeeds + // then subsequent calls will as well. + throw new AssetLoadException("Failed to load image " + assetInfo.getKey(), ex); + } + } + return bitmap; + } + + + + public Format getFormat(){ + return format; + } + + /** + * Loads the bitmap directly from the asset info, possibly updating + * or creating the image object. + */ + protected void loadBitmap() throws IOException{ + InputStream in = null; + try { + in = assetInfo.openStream(); + bitmap = BitmapFactory.decodeStream(in); + if (bitmap == null) { + throw new IOException("Failed to load image: " + assetInfo.getKey().getName()); + } + } finally { + if (in != null) { + in.close(); + } + } + + switch (bitmap.getConfig()) { + case ALPHA_8: + format = Image.Format.Alpha8; + break; + case ARGB_4444: + format = Image.Format.ARGB4444; + break; + case ARGB_8888: + format = Image.Format.RGBA8; + break; + case RGB_565: + format = Image.Format.RGB565; + break; + default: + // This should still work as long + // as renderer doesn't check format + // but just loads bitmap directly. + format = null; + } + + TextureKey texKey = (TextureKey) assetInfo.getKey(); + if (texKey.isFlipY()) { + // Flip the image, then delete the old one. + Matrix flipMat = new Matrix(); + flipMat.preScale(1.0f, -1.0f); + Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), flipMat, false); + bitmap.recycle(); + bitmap = newBitmap; + + if (bitmap == null) { + throw new IOException("Failed to flip image: " + texKey); + } + } + } +} diff --git a/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java b/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java new file mode 100644 index 0000000..01b1cab --- /dev/null +++ b/engine/src/android/com/jme3/asset/plugins/AndroidLocator.java @@ -0,0 +1,90 @@ +package com.jme3.asset.plugins; + +import com.jme3.asset.*; +import com.jme3.system.android.JmeAndroidSystem; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class AndroidLocator implements AssetLocator { + + private static final Logger logger = Logger.getLogger(AndroidLocator.class.getName()); + + private android.content.res.AssetManager androidManager; + private String rootPath = ""; + + private class AndroidAssetInfo extends AssetInfo { + + private InputStream in; + private final String assetPath; + + public AndroidAssetInfo(com.jme3.asset.AssetManager assetManager, AssetKey<?> key, String assetPath, InputStream in) { + super(assetManager, key); + this.assetPath = assetPath; + this.in = in; + } + + @Override + public InputStream openStream() { + if (in != null){ + // Reuse the already existing stream (only once) + InputStream in2 = in; + in = null; + return in2; + }else{ + // Create a new stream for subsequent invocations. + try { + return androidManager.open(assetPath); + } catch (IOException ex) { + throw new AssetLoadException("Failed to open asset " + assetPath, ex); + } + } + } + } + + private AndroidAssetInfo create(AssetManager assetManager, AssetKey key, String assetPath) throws IOException { + try { + InputStream in = androidManager.open(assetPath); + if (in == null){ + return null; + }else{ + return new AndroidAssetInfo(assetManager, key, assetPath, in); + } + } catch (IOException ex) { + // XXX: Prefer to show warning here? + // Should only surpress exceptions for "file missing" type errors. + return null; + } + } + + public AndroidLocator() { + androidManager = JmeAndroidSystem.getResources().getAssets(); + } + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + @SuppressWarnings("rawtypes") + @Override + public AssetInfo locate(com.jme3.asset.AssetManager manager, AssetKey key) { + String assetPath = rootPath + key.getName(); + // Fix path issues + if (assetPath.startsWith("/")) { + // Remove leading / + assetPath = assetPath.substring(1); + } + assetPath = assetPath.replace("//", "/"); + try { + return create(manager, key, assetPath); + }catch (IOException ex){ + // This is different handling than URL locator + // since classpath locating would return null at the getResource() + // call, otherwise there's a more critical error... + throw new AssetLoadException("Failed to open asset " + assetPath, ex); + } + } +} diff --git a/engine/src/android/com/jme3/audio/android/AndroidAudioData.java b/engine/src/android/com/jme3/audio/android/AndroidAudioData.java new file mode 100644 index 0000000..37f41a8 --- /dev/null +++ b/engine/src/android/com/jme3/audio/android/AndroidAudioData.java @@ -0,0 +1,62 @@ +package com.jme3.audio.android;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.audio.AudioData;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.util.NativeObject;
+
+public class AndroidAudioData extends AudioData {
+
+ protected AssetKey<?> assetKey;
+ protected float currentVolume = 0f;
+
+ public AndroidAudioData(){
+ super();
+ }
+
+ protected AndroidAudioData(int id){
+ super(id);
+ }
+
+ public AssetKey<?> getAssetKey() {
+ return assetKey;
+ }
+
+ public void setAssetKey(AssetKey<?> assetKey) {
+ this.assetKey = assetKey;
+ }
+
+ @Override
+ public DataType getDataType() {
+ return DataType.Buffer;
+ }
+
+ @Override
+ public float getDuration() {
+ return 0; // TODO: ???
+ }
+
+ @Override
+ public void resetObject() {
+ this.id = -1;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((AudioRenderer)rendererObject).deleteAudioData(this);
+ }
+
+ public float getCurrentVolume() {
+ return currentVolume;
+ }
+
+ public void setCurrentVolume(float currentVolume) {
+ this.currentVolume = currentVolume;
+ }
+
+ @Override
+ public NativeObject createDestructableClone() {
+ return new AndroidAudioData(id);
+ }
+}
diff --git a/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java new file mode 100644 index 0000000..383def5 --- /dev/null +++ b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java @@ -0,0 +1,498 @@ +/*
+ * Copyright (c) 2009-2010 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.audio.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.util.Log;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.audio.AudioNode.Status;
+import com.jme3.audio.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class is the android implementation for {@link AudioRenderer}
+ *
+ * @author larynx
+ * @author plan_rich
+ */
+public class AndroidAudioRenderer implements AudioRenderer,
+ SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
+
+ private static final Logger logger = Logger.getLogger(AndroidAudioRenderer.class.getName());
+ private final static int MAX_NUM_CHANNELS = 16;
+ private final HashMap<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
+ private SoundPool soundPool = null;
+ private final Vector3f listenerPosition = new Vector3f();
+ // For temp use
+ private final Vector3f distanceVector = new Vector3f();
+ private final Context context;
+ private final AssetManager assetManager;
+ private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();
+ private Listener listener;
+ private boolean audioDisabled = false;
+ private final AudioManager manager;
+
+ public AndroidAudioRenderer(Activity context) {
+ this.context = context;
+ manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ assetManager = context.getAssets();
+ }
+
+ @Override
+ public void initialize() {
+ soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
+ 0);
+ soundPool.setOnLoadCompleteListener(this);
+ }
+
+ @Override
+ public void updateSourceParam(AudioNode src, AudioParam param) {
+ // logger.log(Level.INFO, "updateSourceParam " + param);
+
+ if (audioDisabled) {
+ return;
+ }
+
+ if (src.getChannel() < 0) {
+ return;
+ }
+
+ switch (param) {
+ case Position:
+ if (!src.isPositional()) {
+ return;
+ }
+
+ Vector3f pos = src.getWorldTranslation();
+ break;
+ case Velocity:
+ if (!src.isPositional()) {
+ return;
+ }
+
+ Vector3f vel = src.getVelocity();
+ break;
+ case MaxDistance:
+ if (!src.isPositional()) {
+ return;
+ }
+ break;
+ case RefDistance:
+ if (!src.isPositional()) {
+ return;
+ }
+ break;
+ case ReverbFilter:
+ if (!src.isPositional() || !src.isReverbEnabled()) {
+ return;
+ }
+ break;
+ case ReverbEnabled:
+ if (!src.isPositional()) {
+ return;
+ }
+
+ if (src.isReverbEnabled()) {
+ updateSourceParam(src, AudioParam.ReverbFilter);
+ }
+ break;
+ case IsPositional:
+ break;
+ case Direction:
+ if (!src.isDirectional()) {
+ return;
+ }
+
+ Vector3f dir = src.getDirection();
+ break;
+ case InnerAngle:
+ if (!src.isDirectional()) {
+ return;
+ }
+ break;
+ case OuterAngle:
+ if (!src.isDirectional()) {
+ return;
+ }
+ break;
+ case IsDirectional:
+ if (src.isDirectional()) {
+ updateSourceParam(src, AudioParam.Direction);
+ updateSourceParam(src, AudioParam.InnerAngle);
+ updateSourceParam(src, AudioParam.OuterAngle);
+ } else {
+ }
+ break;
+ case DryFilter:
+ if (src.getDryFilter() != null) {
+ Filter f = src.getDryFilter();
+ if (f.isUpdateNeeded()) {
+ // updateFilter(f);
+ }
+ }
+ break;
+ case Looping:
+ if (src.isLooping()) {
+ }
+ break;
+ case Volume:
+
+ soundPool.setVolume(src.getChannel(), src.getVolume(),
+ src.getVolume());
+
+ break;
+ case Pitch:
+
+ break;
+ }
+
+ }
+
+ @Override
+ public void updateListenerParam(Listener listener, ListenerParam param) {
+ // logger.log(Level.INFO, "updateListenerParam " + param);
+ if (audioDisabled) {
+ return;
+ }
+
+ switch (param) {
+ case Position:
+ listenerPosition.set(listener.getLocation());
+
+ break;
+ case Rotation:
+ Vector3f dir = listener.getDirection();
+ Vector3f up = listener.getUp();
+
+ break;
+ case Velocity:
+ Vector3f vel = listener.getVelocity();
+
+ break;
+ case Volume:
+ // alListenerf(AL_GAIN, listener.getVolume());
+ break;
+ }
+
+ }
+
+ @Override
+ public void update(float tpf) {
+ float distance;
+ float volume;
+
+ // Loop over all mediaplayers
+ for (AudioNode src : musicPlaying.keySet()) {
+
+ MediaPlayer mp = musicPlaying.get(src);
+ {
+ // Calc the distance to the listener
+ distanceVector.set(listenerPosition);
+ distanceVector.subtractLocal(src.getLocalTranslation());
+ distance = FastMath.abs(distanceVector.length());
+
+ if (distance < src.getRefDistance()) {
+ distance = src.getRefDistance();
+ }
+ if (distance > src.getMaxDistance()) {
+ distance = src.getMaxDistance();
+ }
+ volume = src.getRefDistance() / distance;
+
+ AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
+
+ if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {
+ // Left / Right channel get the same volume by now, only
+ // positional
+ mp.setVolume(volume, volume);
+
+ audioData.setCurrentVolume(volume);
+ }
+ }
+ }
+ }
+
+ public void setListener(Listener listener) {
+ if (audioDisabled) {
+ return;
+ }
+
+ if (this.listener != null) {
+ // previous listener no longer associated with current
+ // renderer
+ this.listener.setRenderer(null);
+ }
+
+ this.listener = listener;
+ this.listener.setRenderer(this);
+
+ }
+
+ @Override
+ public void cleanup() {
+ // Cleanup sound pool
+ if (soundPool != null) {
+ soundPool.release();
+ soundPool = null;
+ }
+
+ // Cleanup media player
+ for (AudioNode src : musicPlaying.keySet()) {
+ MediaPlayer mp = musicPlaying.get(src);
+ {
+ mp.stop();
+ mp.release();
+ src.setStatus(Status.Stopped);
+ }
+ }
+ musicPlaying.clear();
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mp.seekTo(0);
+ mp.stop();
+ // XXX: This has bad performance -> maybe change overall structure of
+ // mediaplayer in this audiorenderer?
+ for (AudioNode src : musicPlaying.keySet()) {
+ if (musicPlaying.get(src) == mp) {
+ src.setStatus(Status.Stopped);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Plays using the {@link SoundPool} of Android. Due to hard limitation of
+ * the SoundPool: After playing more instances of the sound you only have
+ * the channel of the last played instance.
+ *
+ * It is not possible to get information about the state of the soundpool of
+ * a specific streamid, so removing is not possilbe -> noone knows when
+ * sound finished.
+ */
+ public void playSourceInstance(AudioNode src) {
+ if (audioDisabled) {
+ return;
+ }
+
+ AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
+
+ if (!(audioData.getAssetKey() instanceof AudioKey)) {
+ throw new IllegalArgumentException("Asset is not a AudioKey");
+ }
+
+ AudioKey assetKey = (AudioKey) audioData.getAssetKey();
+
+ try {
+ if (audioData.getId() < 0) { // found something to load
+ int soundId = soundPool.load(
+ assetManager.openFd(assetKey.getName()), 1);
+ audioData.setId(soundId);
+ }
+
+ int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
+
+ if (channel == 0) {
+ soundpoolStillLoading.put(audioData.getId(), src);
+ } else {
+ src.setChannel(channel); // receive a channel at the last
+ // playing at least
+ }
+ } catch (IOException e) {
+ logger.log(Level.SEVERE,
+ "Failed to load sound " + assetKey.getName(), e);
+ audioData.setId(-1);
+ }
+ }
+
+ @Override
+ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+ AudioNode src = soundpoolStillLoading.remove(sampleId);
+
+ if (src == null) {
+ logger.warning("Something went terribly wrong! onLoadComplete"
+ + " had sampleId which was not in the HashMap of loading items");
+ return;
+ }
+
+ AudioData audioData = src.getAudioData();
+
+ if (status == 0) // load was successfull
+ {
+ int channelIndex;
+ channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
+ src.setChannel(channelIndex);
+ }
+ }
+
+ public void playSource(AudioNode src) {
+ if (audioDisabled) {
+ return;
+ }
+
+ AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
+
+ MediaPlayer mp = musicPlaying.get(src);
+ if (mp == null) {
+ mp = new MediaPlayer();
+ mp.setOnCompletionListener(this);
+ mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ }
+
+ try {
+ AssetKey<?> key = audioData.getAssetKey();
+
+ AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
+ mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+ afd.getLength());
+ mp.prepare();
+ mp.setLooping(src.isLooping());
+ mp.start();
+ src.setChannel(0);
+ src.setStatus(Status.Playing);
+ musicPlaying.put(src, mp);
+
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Pause the current playing sounds. Both from the {@link SoundPool} and the
+ * active {@link MediaPlayer}s
+ */
+ public void pauseAll() {
+ if (soundPool != null) {
+ soundPool.autoPause();
+ for (MediaPlayer mp : musicPlaying.values()) {
+ mp.pause();
+ }
+ }
+ }
+
+ /**
+ * Resume all paused sounds.
+ */
+ public void resumeAll() {
+ if (soundPool != null) {
+ soundPool.autoResume();
+ for (MediaPlayer mp : musicPlaying.values()) {
+ mp.start(); //no resume -> api says call start to resume
+ }
+ }
+ }
+
+ public void pauseSource(AudioNode src) {
+ if (audioDisabled) {
+ return;
+ }
+
+ MediaPlayer mp = musicPlaying.get(src);
+ if (mp != null) {
+ mp.pause();
+ src.setStatus(Status.Paused);
+ } else {
+ int channel = src.getChannel();
+ if (channel != -1) {
+ soundPool.pause(channel); // is not very likley to make
+ } // something useful :)
+ }
+ }
+
+ public void stopSource(AudioNode src) {
+ if (audioDisabled) {
+ return;
+ }
+
+ // can be stream or buffer -> so try to get mediaplayer
+ // if there is non try to stop soundpool
+ MediaPlayer mp = musicPlaying.get(src);
+ if (mp != null) {
+ mp.stop();
+ src.setStatus(Status.Paused);
+ } else {
+ int channel = src.getChannel();
+ if (channel != -1) {
+ soundPool.pause(channel); // is not very likley to make
+ // something useful :)
+ }
+ }
+
+ }
+
+ @Override
+ public void deleteAudioData(AudioData ad) {
+
+ for (AudioNode src : musicPlaying.keySet()) {
+ if (src.getAudioData() == ad) {
+ MediaPlayer mp = musicPlaying.remove(src);
+ mp.stop();
+ mp.release();
+ src.setStatus(Status.Stopped);
+ src.setChannel(-1);
+ ad.setId(-1);
+ break;
+ }
+ }
+
+ if (ad.getId() > 0) {
+ soundPool.unload(ad.getId());
+ ad.setId(-1);
+ }
+ }
+
+ @Override
+ public void setEnvironment(Environment env) {
+ // not yet supported
+ }
+
+ @Override
+ public void deleteFilter(Filter filter) {
+ }
+}
diff --git a/engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java b/engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java new file mode 100644 index 0000000..fc9c03e --- /dev/null +++ b/engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java @@ -0,0 +1,19 @@ +package com.jme3.audio.plugins;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.audio.android.AndroidAudioData;
+import java.io.IOException;
+
+public class AndroidAudioLoader implements AssetLoader
+{
+
+ @Override
+ public Object load(AssetInfo assetInfo) throws IOException
+ {
+ AndroidAudioData result = new AndroidAudioData();
+ result.setAssetKey( assetInfo.getKey() );
+ return result;
+ }
+
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidInput.java b/engine/src/android/com/jme3/input/android/AndroidInput.java new file mode 100644 index 0000000..ca1141d --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidInput.java @@ -0,0 +1,619 @@ +package com.jme3.input.android; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.input.event.TouchEvent.Type; +import com.jme3.math.Vector2f; +import com.jme3.util.RingBuffer; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * <code>AndroidInput</code> is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs + * @author larynx + * + */ +public class AndroidInput extends GLSurfaceView implements TouchInput, + GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener { + + final private static int MAX_EVENTS = 1024; + // Custom settings + public boolean mouseEventsEnabled = true; + public boolean mouseEventsInvertX = false; + public boolean mouseEventsInvertY = false; + public boolean keyboardEventsEnabled = false; + public boolean dontSendHistory = false; + // Used to transfer events from android thread to GLThread + final private RingBuffer<TouchEvent> eventQueue = new RingBuffer<TouchEvent>(MAX_EVENTS); + final private RingBuffer<TouchEvent> eventPoolUnConsumed = new RingBuffer<TouchEvent>(MAX_EVENTS); + final private RingBuffer<TouchEvent> eventPool = new RingBuffer<TouchEvent>(MAX_EVENTS); + final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>(); + // Internal + private ScaleGestureDetector scaledetector; + private GestureDetector detector; + private int lastX; + private int lastY; + private final static Logger logger = Logger.getLogger(AndroidInput.class.getName()); + private boolean isInitialized = false; + private RawInputListener listener = null; + private static final int[] ANDROID_TO_JME = { + 0x0, // unknown + 0x0, // key code soft left + 0x0, // key code soft right + KeyInput.KEY_HOME, + KeyInput.KEY_ESCAPE, // key back + 0x0, // key call + 0x0, // key endcall + KeyInput.KEY_0, + KeyInput.KEY_1, + KeyInput.KEY_2, + KeyInput.KEY_3, + KeyInput.KEY_4, + KeyInput.KEY_5, + KeyInput.KEY_6, + KeyInput.KEY_7, + KeyInput.KEY_8, + KeyInput.KEY_9, + KeyInput.KEY_MULTIPLY, + 0x0, // key pound + KeyInput.KEY_UP, + KeyInput.KEY_DOWN, + KeyInput.KEY_LEFT, + KeyInput.KEY_RIGHT, + KeyInput.KEY_RETURN, // dpad center + 0x0, // volume up + 0x0, // volume down + KeyInput.KEY_POWER, // power (?) + 0x0, // camera + 0x0, // clear + KeyInput.KEY_A, + KeyInput.KEY_B, + KeyInput.KEY_C, + KeyInput.KEY_D, + KeyInput.KEY_E, + KeyInput.KEY_F, + KeyInput.KEY_G, + KeyInput.KEY_H, + KeyInput.KEY_I, + KeyInput.KEY_J, + KeyInput.KEY_K, + KeyInput.KEY_L, + KeyInput.KEY_M, + KeyInput.KEY_N, + KeyInput.KEY_O, + KeyInput.KEY_P, + KeyInput.KEY_Q, + KeyInput.KEY_R, + KeyInput.KEY_S, + KeyInput.KEY_T, + KeyInput.KEY_U, + KeyInput.KEY_V, + KeyInput.KEY_W, + KeyInput.KEY_X, + KeyInput.KEY_Y, + KeyInput.KEY_Z, + KeyInput.KEY_COMMA, + KeyInput.KEY_PERIOD, + KeyInput.KEY_LMENU, + KeyInput.KEY_RMENU, + KeyInput.KEY_LSHIFT, + KeyInput.KEY_RSHIFT, + // 0x0, // fn + // 0x0, // cap (?) + + KeyInput.KEY_TAB, + KeyInput.KEY_SPACE, + 0x0, // sym (?) symbol + 0x0, // explorer + 0x0, // envelope + KeyInput.KEY_RETURN, // newline/enter + KeyInput.KEY_DELETE, + KeyInput.KEY_GRAVE, + KeyInput.KEY_MINUS, + KeyInput.KEY_EQUALS, + KeyInput.KEY_LBRACKET, + KeyInput.KEY_RBRACKET, + KeyInput.KEY_BACKSLASH, + KeyInput.KEY_SEMICOLON, + KeyInput.KEY_APOSTROPHE, + KeyInput.KEY_SLASH, + KeyInput.KEY_AT, // at (@) + KeyInput.KEY_NUMLOCK, //0x0, // num + 0x0, //headset hook + 0x0, //focus + KeyInput.KEY_ADD, + KeyInput.KEY_LMETA, //menu + 0x0,//notification + 0x0,//search + 0x0,//media play/pause + 0x0,//media stop + 0x0,//media next + 0x0,//media previous + 0x0,//media rewind + 0x0,//media fastforward + 0x0,//mute + }; + + public AndroidInput(Context ctx, AttributeSet attribs) { + super(ctx, attribs); + detector = new GestureDetector(null, this, null, false); + scaledetector = new ScaleGestureDetector(ctx, this); + + } + + public AndroidInput(Context ctx) { + super(ctx); + detector = new GestureDetector(null, this, null, false); + scaledetector = new ScaleGestureDetector(ctx, this); + } + + private TouchEvent getNextFreeTouchEvent() { + return getNextFreeTouchEvent(false); + } + + /** + * Fetches a touch event from the reuse pool + * @param wait if true waits for a reusable event to get available/released by an other thread, if false returns a new one if needed + * @return a usable TouchEvent + */ + private TouchEvent getNextFreeTouchEvent(boolean wait) { + TouchEvent evt = null; + synchronized (eventPoolUnConsumed) { + int size = eventPoolUnConsumed.size(); + while (size > 0) { + evt = eventPoolUnConsumed.pop(); + if (!evt.isConsumed()) { + eventPoolUnConsumed.push(evt); + evt = null; + } else { + break; + } + size--; + } + } + + + if (evt == null) { + if (eventPool.isEmpty() && wait) { + logger.warning("eventPool buffer underrun"); + boolean isEmpty; + do { + synchronized (eventPool) { + isEmpty = eventPool.isEmpty(); + } + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + } while (isEmpty); + synchronized (eventPool) { + evt = eventPool.pop(); + } + } else if (eventPool.isEmpty()) { + evt = new TouchEvent(); + logger.warning("eventPool buffer underrun"); + } else { + synchronized (eventPool) { + evt = eventPool.pop(); + } + } + } + return evt; + } + + /** + * onTouchEvent gets called from android thread on touchpad events + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean bWasHandled = false; + TouchEvent touch; + // System.out.println("native : " + event.getAction()); + int action = event.getAction() & MotionEvent.ACTION_MASK; + int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + int pointerId = event.getPointerId(pointerIndex); + + // final int historySize = event.getHistorySize(); + //final int pointerCount = event.getPointerCount(); + + + switch (action) { + + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + + + touch = getNextFreeTouchEvent(); + touch.set(Type.DOWN, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + + bWasHandled = true; + break; + + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + + touch = getNextFreeTouchEvent(); + touch.set(Type.UP, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + + + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + Vector2f lastPos = lastPositions.get(pointerIndex); + if (lastPos == null) { + lastPos = new Vector2f(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex)); + lastPositions.put(pointerId, lastPos); + } + touch = getNextFreeTouchEvent(); + touch.set(Type.MOVE, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), event.getX(pointerIndex) - lastPos.x, this.getHeight() - event.getY(pointerIndex) - lastPos.y); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + lastPos.set(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex)); + } + bWasHandled = true; + break; + + + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + this.detector.onTouchEvent(event); + this.scaledetector.onTouchEvent(event); + + return bWasHandled; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); + + // Handle all keys ourself except Volume Up/Down + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { + return false; + } else { + return true; + } + + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); + + // Handle all keys ourself except Volume Up/Down + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { + return false; + } else { + return true; + } + } + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + TouchEvent item; + for (int i = 0; i < MAX_EVENTS; i++) { + item = new TouchEvent(); + eventPool.push(item); + } + isInitialized = true; + } + + @Override + public void destroy() { + isInitialized = false; + + // Clean up queues + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + while (!eventQueue.isEmpty()) { + eventQueue.pop(); + } + } + + @Override + public boolean isInitialized() { + return isInitialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + // ----------------------------------------- + + private void processEvent(TouchEvent event) { + synchronized (eventQueue) { + eventQueue.push(event); + } + } + + // --------------- INSIDE GLThread --------------- + @Override + public void update() { + generateEvents(); + } + + private void generateEvents() { + if (listener != null) { + TouchEvent event; + MouseButtonEvent btn; + int newX; + int newY; + + while (!eventQueue.isEmpty()) { + synchronized (eventQueue) { + event = eventQueue.pop(); + } + if (event != null) { + listener.onTouchEvent(event); + + if (mouseEventsEnabled) { + if (mouseEventsInvertX) { + newX = this.getWidth() - (int) event.getX(); + } else { + newX = (int) event.getX(); + } + + if (mouseEventsInvertY) { + newY = this.getHeight() - (int) event.getY(); + } else { + newY = (int) event.getY(); + } + + switch (event.getType()) { + case DOWN: + // Handle mouse down event + btn = new MouseButtonEvent(0, true, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case UP: + // Handle mouse up event + btn = new MouseButtonEvent(0, false, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case MOVE: + int dx; + int dy; + if (lastX != -1) { + dx = newX - lastX; + dy = newY - lastY; + } else { + dx = 0; + dy = 0; + } + MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0); + mot.setTime(event.getTime()); + listener.onMouseMotionEvent(mot); + lastX = newX; + lastY = newY; + break; + } + + + } + } + + if (event.isConsumed() == false) { + synchronized (eventPoolUnConsumed) { + eventPoolUnConsumed.push(event); + } + + } else { + synchronized (eventPool) { + eventPool.push(event); + } + } + } + + } + } + // --------------- ENDOF INSIDE GLThread --------------- + + // --------------- Gesture detected callback events --------------- + public boolean onDown(MotionEvent event) { + return false; + } + + public void onLongPress(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.LONGPRESSED, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + } + + public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.FLING, event.getX(), this.getHeight() - event.getY(), vx, vy); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + + return true; + } + + public boolean onSingleTapConfirmed(MotionEvent event) { + //Nothing to do here the tap has already been detected. + return false; + } + + public boolean onDoubleTap(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.DOUBLETAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + return true; + } + + public boolean onDoubleTapEvent(MotionEvent event) { + return false; + } + + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + // System.out.println("scaleBegin"); + + return true; + } + + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + // System.out.println("scale"); + + return false; + } + + public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCROLL, e1.getX(), this.getHeight() - e1.getY(), distanceX, distanceY * (-1)); + touch.setPointerId(0); + touch.setTime(e1.getEventTime()); + processEvent(touch); + //System.out.println("scroll " + e1.getPointerCount()); + return false; + } + + public void onShowPress(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SHOWPRESS, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + } + + public boolean onSingleTapUp(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.TAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + return true; + } + + @Override + public void setSimulateMouse(boolean simulate) { + mouseEventsEnabled = simulate; + } + + @Override + public void setSimulateKeyboard(boolean simulate) { + keyboardEventsEnabled = simulate; + } + + @Override + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + + // TODO: move to TouchInput + public boolean isMouseEventsEnabled() { + return mouseEventsEnabled; + } + + public void setMouseEventsEnabled(boolean mouseEventsEnabled) { + this.mouseEventsEnabled = mouseEventsEnabled; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public void setMouseEventsInvertY(boolean mouseEventsInvertY) { + this.mouseEventsInvertY = mouseEventsInvertY; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public void setMouseEventsInvertX(boolean mouseEventsInvertX) { + this.mouseEventsInvertX = mouseEventsInvertX; + } +} diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java new file mode 100644 index 0000000..34e4592 --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java @@ -0,0 +1,19 @@ +package com.jme3.input.android; + +import android.view.KeyEvent; +import android.view.MotionEvent; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.TouchEvent; + +/** + * AndroidTouchInputListener is an inputlistener interface which defines callbacks/events for android touch screens + * For use with class AndroidInput + * @author larynx + * + */ +public interface AndroidTouchInputListener extends RawInputListener +{ + public void onTouchEvent(TouchEvent evt); + public void onMotionEvent(MotionEvent evt); + public void onAndroidKeyEvent(KeyEvent evt); +} diff --git a/engine/src/android/com/jme3/renderer/android/Android22Workaround.java b/engine/src/android/com/jme3/renderer/android/Android22Workaround.java new file mode 100644 index 0000000..9c5bf58 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/Android22Workaround.java @@ -0,0 +1,14 @@ +package com.jme3.renderer.android; + +import android.opengl.GLES20; + +public class Android22Workaround { + public static void glVertexAttribPointer(int location, int components, int format, boolean normalize, int stride, int offset){ + GLES20.glVertexAttribPointer(location, + components, + format, + normalize, + stride, + offset); + } +} diff --git a/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java new file mode 100644 index 0000000..b5a8c14 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java @@ -0,0 +1,2951 @@ +/* + * Copyright (c) 2009-2010 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.renderer.android; + +import android.graphics.Bitmap; +import android.opengl.GLES10; +import android.opengl.GLES11; +import android.opengl.GLES20; +import android.os.Build; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.math.*; +import com.jme3.renderer.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.shader.Attribute; +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.shader.Shader.ShaderType; +import com.jme3.shader.Uniform; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.RenderBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.util.BufferUtils; +import com.jme3.util.ListMap; +import com.jme3.util.NativeObjectManager; +import com.jme3.util.SafeArrayList; +import java.nio.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.microedition.khronos.opengles.GL10; + +public class OGLESShaderRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(OGLESShaderRenderer.class.getName()); + private static final boolean VALIDATE_SHADER = false; + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final StringBuilder stringBuf = new StringBuilder(250); + private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); + private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + private final RenderContext context = new RenderContext(); + private final NativeObjectManager objManager = new NativeObjectManager(); + private final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class); + // current state + private Shader boundShader; + private int initialDrawBuf, initialReadBuf; + private int glslVer; + private int vertexTextureUnits; + private int fragTextureUnits; + private int vertexUniforms; + private int fragUniforms; + private int vertexAttribs; + private int maxFBOSamples; + private int maxFBOAttachs; + private int maxMRTFBOAttachs; + private int maxRBSize; + private int maxTexSize; + private int maxCubeTexSize; + private int maxVertCount; + private int maxTriCount; + private boolean tdc; + private FrameBuffer lastFb = null; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + //private final GL10 gl; + private boolean powerVr = false; + private boolean powerOf2 = false; + private boolean verboseLogging = false; + private boolean useVBO = false; + private boolean checkErrors = true; + + public OGLESShaderRenderer() { + } + + public void setUseVA(boolean value) { + logger.log(Level.INFO, "use_VBO [{0}] -> [{1}]", new Object[]{useVBO, !value}); + useVBO = !value; + } + + public void setVerboseLogging(boolean value) { + logger.log(Level.INFO, "verboseLogging [{0}] -> [{1}]", new Object[]{verboseLogging, value}); + verboseLogging = value; + } + + protected void updateNameBuffer() { + int len = stringBuf.length(); + + nameBuf.position(0); + nameBuf.limit(len); + for (int i = 0; i < len; i++) { + nameBuf.put((byte) stringBuf.charAt(i)); + } + + nameBuf.rewind(); + } + + private void checkGLError() { + if (!checkErrors) return; + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + throw new RendererException("OpenGL Error " + error); + } + } + + private boolean log(String message) { + logger.info(message); + return true; + } + + public Statistics getStatistics() { + return statistics; + } + + public EnumSet<Caps> getCaps() { + return caps; + } + + public void initialize() { + + logger.log(Level.INFO, "Vendor: {0}", GLES20.glGetString(GLES20.GL_VENDOR)); + logger.log(Level.INFO, "Renderer: {0}", GLES20.glGetString(GLES20.GL_RENDERER)); + logger.log(Level.INFO, "Version: {0}", GLES20.glGetString(GLES20.GL_VERSION)); + + powerVr = GLES20.glGetString(GLES20.GL_RENDERER).contains("PowerVR"); + + String versionStr = GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION); + logger.log(Level.INFO, "GLES20.Shading Language Version: {0}", versionStr); + if (versionStr == null || versionStr.equals("")) { + glslVer = -1; + throw new UnsupportedOperationException("GLSL and OpenGL2 is " + + "required for the OpenGL ES " + + "renderer!"); + } + + // Fix issue in TestRenderToMemory when GL_FRONT is the main + // buffer being used. + +// initialDrawBuf = GLES20.glGetIntegeri(GLES20.GL_DRAW_BUFFER); +// initialReadBuf = GLES20.glGetIntegeri(GLES20.GL_READ_BUFFER); + + String openGlEsStr = "OpenGL ES GLSL ES "; + int spaceIdx = versionStr.indexOf(" ", openGlEsStr.length()); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(openGlEsStr.length(), spaceIdx).trim(); + }else{ + versionStr = versionStr.substring(openGlEsStr.length()).trim(); + } + + float version = Float.parseFloat(versionStr); + glslVer = (int) (version * 100); + + switch (glslVer) { + // TODO: When new versions of OpenGL ES shader language come out, + // update this. + default: + caps.add(Caps.GLSL100); + break; + } + + if (!caps.contains(Caps.GLSL100)) { + logger.info("Force-adding GLSL100 support, since OpenGL2 is supported."); + caps.add(Caps.GLSL100); + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); + vertexTextureUnits = intBuf16.get(0); + logger.log(Level.INFO, "VTF Units: {0}", vertexTextureUnits); + if (vertexTextureUnits > 0) { + caps.add(Caps.VertexTextureFetch); + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); + fragTextureUnits = intBuf16.get(0); + logger.log(Level.INFO, "Texture Units: {0}", fragTextureUnits); + /* + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16); + vertexUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); + + GLES20.glGetIntegerv(GLES20.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); + fragUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); + */ + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_ATTRIBS, intBuf16); + vertexAttribs = intBuf16.get(0); + logger.log(Level.INFO, "Vertex Attributes: {0}", vertexAttribs); + + /* + GLES20.glGetIntegerv(GLES20.GL_MAX_VARYING_FLOATS, intBuf16); + int varyingFloats = intBuf16.get(0); + logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); + */ + + GLES20.glGetIntegerv(GLES20.GL_SUBPIXEL_BITS, intBuf16); + int subpixelBits = intBuf16.get(0); + logger.log(Level.INFO, "Subpixel Bits: {0}", subpixelBits); + /* + GLES20.glGetIntegerv(GLES20.GL_MAX_ELEMENTS_VERTICES, intBuf16); + maxVertCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); + + GLES20.glGetIntegerv(GLES20.GL_MAX_ELEMENTS_INDICES, intBuf16); + maxTriCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); + */ + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, intBuf16); + maxTexSize = intBuf16.get(0); + logger.log(Level.INFO, "Maximum Texture Resolution: {0}", maxTexSize); + + GLES20.glGetIntegerv(GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); + maxCubeTexSize = intBuf16.get(0); + logger.log(Level.INFO, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); + + + /* + if (ctxCaps.GL_ARB_color_buffer_float){ + // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. + if (ctxCaps.GL_ARB_half_float_pixel){ + caps.add(Caps.FloatColorBuffer); + } + } + + if (ctxCaps.GL_ARB_depth_buffer_float){ + caps.add(Caps.FloatDepthBuffer); + } + + if (ctxCaps.GL_ARB_draw_instanced) + caps.add(Caps.MeshInstancing); + + if (ctxCaps.GL_ARB_fragment_program) + caps.add(Caps.ARBprogram); + + if (ctxCaps.GL_ARB_texture_buffer_object) + caps.add(Caps.TextureBuffer); + + if (ctxCaps.GL_ARB_texture_float){ + if (ctxCaps.GL_ARB_half_float_pixel){ + caps.add(Caps.FloatTexture); + } + } + + if (ctxCaps.GL_ARB_vertex_array_object) + caps.add(Caps.VertexBufferArray); + + boolean latc = ctxCaps.GL_EXT_texture_compression_latc; + boolean atdc = ctxCaps.GL_ATI_texture_compression_3dc; + if (latc || atdc){ + caps.add(Caps.TextureCompressionLATC); + if (atdc && !latc){ + tdc = true; + } + } + + if (ctxCaps.GL_EXT_packed_float){ + caps.add(Caps.PackedFloatColorBuffer); + if (ctxCaps.GL_ARB_half_float_pixel){ + // because textures are usually uploaded as RGB16F + // need half-float pixel + caps.add(Caps.PackedFloatTexture); + } + } + + if (ctxCaps.GL_EXT_texture_array) + caps.add(Caps.TextureArray); + + if (ctxCaps.GL_EXT_texture_shared_exponent) + caps.add(Caps.SharedExponentTexture); + + if (ctxCaps.GL_EXT_framebuffer_object){ + caps.add(Caps.FrameBuffer); + + glGetInteger(GL_MAX_RENDERBUFFER_SIZE_EXT, intBuf16); + maxRBSize = intBuf16.get(0); + logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); + + glGetInteger(GL_MAX_COLOR_ATTACHMENTS_EXT, intBuf16); + maxFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs); + + if (ctxCaps.GL_EXT_framebuffer_multisample){ + caps.add(Caps.FrameBufferMultisample); + + glGetInteger(GL_MAX_SAMPLES_EXT, intBuf16); + maxFBOSamples = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples); + } + + if (ctxCaps.GL_ARB_draw_buffers){ + caps.add(Caps.FrameBufferMRT); + glGetInteger(ARBDrawBuffers.GL_MAX_DRAW_BUFFERS_ARB, intBuf16); + maxMRTFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); + } + } + + if (ctxCaps.GL_ARB_multisample){ + glGetInteger(ARBMultisample.GL_SAMPLE_BUFFERS_ARB, intBuf16); + boolean available = intBuf16.get(0) != 0; + glGetInteger(ARBMultisample.GL_SAMPLES_ARB, intBuf16); + int samples = intBuf16.get(0); + logger.log(Level.FINER, "Samples: {0}", samples); + boolean enabled = glIsEnabled(ARBMultisample.GL_MULTISAMPLE_ARB); + if (samples > 0 && available && !enabled){ + glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + */ + + String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS); + logger.log(Level.INFO, "GL_EXTENSIONS: {0}", extensions); + + GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, intBuf16); + for (int i = 0; i < intBuf16.limit(); i++) { + logger.log(Level.INFO, "Compressed Texture Formats: {0}", intBuf16.get(i)); + } + + if (extensions.contains("GL_OES_texture_npot")) { + powerOf2 = true; + } + + applyRenderState(RenderState.DEFAULT); +// GLES20.glClearDepthf(1.0f); + + if (verboseLogging) { + logger.info("GLES20.glDisable(GL10.GL_DITHER)"); + } + + GLES20.glDisable(GL10.GL_DITHER); + + checkGLError(); + + if (verboseLogging) { + logger.info("GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST)"); + } + + //It seems that GL10.GL_PERSPECTIVE_CORRECTION_HINT gives invalid_enum error on android. +// GLES20.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); + +// checkGLError(); + + useVBO = false; + + // NOTE: SDK_INT is only available since 1.6, + // but for jME3 it doesn't matter since android versions 1.5 and below + // are not supported. + if (Build.VERSION.SDK_INT >= 9){ + useVBO = true; + } + + logger.log(Level.INFO, "Caps: {0}", caps); + } + + /** + * <code>resetGLObjects</code> should be called when die GLView gets recreated to reset all GPU objects + */ + public void resetGLObjects() { + objManager.resetObjects(); + statistics.clearMemory(); + boundShader = null; + lastFb = null; + context.reset(); + } + + public void cleanup() { + objManager.deleteAllObjects(this); + statistics.clearMemory(); + } + + private void checkCap(Caps cap) { + if (!caps.contains(cap)) { + throw new UnsupportedOperationException("Required capability missing: " + cap.name()); + } + } + + /*********************************************************************\ + |* Render State *| + \*********************************************************************/ + public void setDepthRange(float start, float end) { + + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glDepthRangef({0}, {1})", new Object[]{start, end}); + } + GLES20.glDepthRangef(start, end); + checkGLError(); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + bits = GLES20.GL_COLOR_BUFFER_BIT; + } + if (depth) { + bits |= GLES20.GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= GLES20.GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glClear(color={0}, depth={1}, stencil={2})", new Object[]{color, depth, stencil}); + } + GLES20.glClear(bits); + checkGLError(); + } + } + + public void setBackgroundColor(ColorRGBA color) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glClearColor({0}, {1}, {2}, {3})", new Object[]{color.r, color.g, color.b, color.a}); + } + GLES20.glClearColor(color.r, color.g, color.b, color.a); + checkGLError(); + } + + public void applyRenderState(RenderState state) { + /* + if (state.isWireframe() && !context.wireframe){ + GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_LINE); + context.wireframe = true; + }else if (!state.isWireframe() && context.wireframe){ + GLES20.glPolygonMode(GLES20.GL_FRONT_AND_BACK, GLES20.GL_FILL); + context.wireframe = false; + } + */ + if (state.isDepthTest() && !context.depthTestEnabled) { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_DEPTH_TEST)"); + } + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + checkGLError(); + if (verboseLogging) { + logger.info("GLES20.glDepthFunc(GLES20.GL_LEQUAL)"); + } + GLES20.glDepthFunc(GLES20.GL_LEQUAL); + checkGLError(); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_DEPTH_TEST)"); + } + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + checkGLError(); + context.depthTestEnabled = false; + } + if (state.isAlphaTest() && !context.alphaTestEnabled) { +// GLES20.glEnable(GLES20.GL_ALPHA_TEST); +// GLES20.glAlphaFunc(GLES20.GL_GREATER, state.getAlphaFallOff()); + context.alphaTestEnabled = true; + } else if (!state.isAlphaTest() && context.alphaTestEnabled) { +// GLES20.glDisable(GLES20.GL_ALPHA_TEST); + context.alphaTestEnabled = false; + } + if (state.isDepthWrite() && !context.depthWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDepthMask(true)"); + } + GLES20.glDepthMask(true); + checkGLError(); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDepthMask(false)"); + } + GLES20.glDepthMask(false); + checkGLError(); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glColorMask(true, true, true, true)"); + } + GLES20.glColorMask(true, true, true, true); + checkGLError(); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + if (verboseLogging) { + logger.info("GLES20.glColorMask(false, false, false, false)"); + } + GLES20.glColorMask(false, false, false, false); + checkGLError(); + context.colorWriteEnabled = false; + } + if (state.isPointSprite() && !context.pointSprite) { +// GLES20.glEnable(GLES20.GL_POINT_SPRITE); +// GLES20.glTexEnvi(GLES20.GL_POINT_SPRITE, GLES20.GL_COORD_REPLACE, GLES20.GL_TRUE); +// GLES20.glEnable(GLES20.GL_VERTEX_PROGRAM_POINT_SIZE); +// GLES20.glPointParameterf(GLES20.GL_POINT_SIZE_MIN, 1.0f); + } else if (!state.isPointSprite() && context.pointSprite) { +// GLES20.glDisable(GLES20.GL_POINT_SPRITE); + } + + if (state.isPolyOffset()) { + if (!context.polyOffsetEnabled) { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL)"); + } + GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL); + checkGLError(); + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glPolygonOffset({0}, {1})", new Object[]{state.getPolyOffsetFactor(), state.getPolyOffsetUnits()}); + } + GLES20.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + checkGLError(); + context.polyOffsetEnabled = true; + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } else { + if (state.getPolyOffsetFactor() != context.polyOffsetFactor + || state.getPolyOffsetUnits() != context.polyOffsetUnits) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glPolygonOffset({0}, {1})", new Object[]{state.getPolyOffsetFactor(), state.getPolyOffsetUnits()}); + } + GLES20.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + checkGLError(); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL)"); + } + GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL); + checkGLError(); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_CULL_FACE)"); + } + GLES20.glDisable(GLES20.GL_CULL_FACE); + } else { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_CULL_FACE)"); + } + GLES20.glEnable(GLES20.GL_CULL_FACE); + } + + checkGLError(); + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + if (verboseLogging) { + logger.info("GLES20.glCullFace(GLES20.GL_BACK)"); + } + GLES20.glCullFace(GLES20.GL_BACK); + break; + case Front: + if (verboseLogging) { + logger.info("GLES20.glCullFace(GLES20.GL_FRONT)"); + } + GLES20.glCullFace(GLES20.GL_FRONT); + break; + case FrontAndBack: + if (verboseLogging) { + logger.info("GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK)"); + } + GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK); + break; + default: + throw new UnsupportedOperationException("Unrecognized face cull mode: " + + state.getFaceCullMode()); + } + + checkGLError(); + + context.cullMode = state.getFaceCullMode(); + } + + if (state.getBlendMode() != context.blendMode) { + if (state.getBlendMode() == RenderState.BlendMode.Off) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_BLEND)"); + } + GLES20.glDisable(GLES20.GL_BLEND); + } else { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_BLEND)"); + } + GLES20.glEnable(GLES20.GL_BLEND); + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE)"); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); + break; + case AlphaAdditive: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE)"); + } + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); + break; + case Color: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR)"); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR); + break; + case Alpha: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)"); + } + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)"); + } + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO)"); + } + GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO); + break; + case ModulateX2: + if (verboseLogging) { + logger.info("GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR)"); + } + GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + } + + checkGLError(); + + context.blendMode = state.getBlendMode(); + } + } + + /*********************************************************************\ + |* Camera and World transforms *| + \*********************************************************************/ + public void setViewPort(int x, int y, int w, int h) { + if (x != vpX || vpY != y || vpW != w || vpH != h) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glViewport({0}, {1}, {2}, {3})", new Object[]{x, y, w, h}); + } + GLES20.glViewport(x, y, w, h); + checkGLError(); + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + if (verboseLogging) { + logger.info("GLES20.glEnable(GLES20.GL_SCISSOR_TEST)"); + } + GLES20.glEnable(GLES20.GL_SCISSOR_TEST); + checkGLError(); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glScissor({0}, {1}, {2}, {3})", new Object[]{x, y, width, height}); + } + GLES20.glScissor(x, y, width, height); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + checkGLError(); + } + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + if (verboseLogging) { + logger.info("GLES20.glDisable(GLES20.GL_SCISSOR_TEST)"); + } + GLES20.glDisable(GLES20.GL_SCISSOR_TEST); + checkGLError(); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + if (!checkErrors){ + int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR){ + throw new RendererException("OpenGL Error " + error + ". Enable error checking for more info."); + } + } + objManager.deleteUnused(this); +// statistics.clearFrame(); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + } + + /*********************************************************************\ + |* Shaders *| + \*********************************************************************/ + protected void updateUniformLocation(Shader shader, Uniform uniform) { + stringBuf.setLength(0); + stringBuf.append(uniform.getName()).append('\0'); + updateNameBuffer(); + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glGetUniformLocation({0}, {1})", new Object[]{shader.getId(), uniform.getName()}); + } + int loc = GLES20.glGetUniformLocation(shader.getId(), uniform.getName()); + checkGLError(); + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + if (verboseLogging) { + logger.log(Level.WARNING, "Uniform [{0}] is not declared in shader.", uniform.getName()); + } + } else { + uniform.setLocation(loc); + } + } + + protected void updateUniform(Shader shader, Uniform uniform) { + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shader.getId() > 0; + + if (context.boundShaderProgram != shaderId) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glUseProgram({0})", shaderId); + } + GLES20.glUseProgram(shaderId); + checkGLError(); + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + + int loc = uniform.getLocation(); + if (loc == -1) { + if (verboseLogging) { + logger.log(Level.WARNING, "no location for uniform [{0}]", uniform.getName()); + } + return; + } + + if (loc == -2) { + // get uniform location + updateUniformLocation(shader, uniform); + if (uniform.getLocation() == -1) { + // not declared, ignore + + if (verboseLogging) { + logger.log(Level.WARNING, "not declared uniform: [{0}]", uniform.getName()); + } + + uniform.clearUpdateNeeded(); + return; + } + loc = uniform.getLocation(); + } + + if (uniform.getVarType() == null) { + logger.warning("value is not set yet."); + return; // value not set yet.. + } + + statistics.onUniformSet(); + + uniform.clearUpdateNeeded(); + FloatBuffer fb; + switch (uniform.getVarType()) { + case Float: + if (verboseLogging) { + logger.info("GLES20.glUniform1f set Float. " + uniform.getName()); + } + Float f = (Float) uniform.getValue(); + GLES20.glUniform1f(loc, f.floatValue()); + break; + case Vector2: + if (verboseLogging) { + logger.info("GLES20.glUniform2f set Vector2. " + uniform.getName()); + } + Vector2f v2 = (Vector2f) uniform.getValue(); + GLES20.glUniform2f(loc, v2.getX(), v2.getY()); + break; + case Vector3: + if (verboseLogging) { + logger.info("GLES20.glUniform3f set Vector3. " + uniform.getName()); + } + Vector3f v3 = (Vector3f) uniform.getValue(); + GLES20.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); + break; + case Vector4: + if (verboseLogging) { + logger.info("GLES20.glUniform4f set Vector4." + uniform.getName()); + } + Object val = uniform.getValue(); + if (val instanceof ColorRGBA) { + ColorRGBA c = (ColorRGBA) val; + GLES20.glUniform4f(loc, c.r, c.g, c.b, c.a); + } else { + Quaternion c = (Quaternion) uniform.getValue(); + GLES20.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); + } + break; + case Boolean: + if (verboseLogging) { + logger.info("GLES20.glUniform1i set Boolean." + uniform.getName()); + } + Boolean b = (Boolean) uniform.getValue(); + GLES20.glUniform1i(loc, b.booleanValue() ? GLES20.GL_TRUE : GLES20.GL_FALSE); + break; + case Matrix3: + if (verboseLogging) { + logger.info("GLES20.glUniformMatrix3fv set Matrix3." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 9; + GLES20.glUniformMatrix3fv(loc, 1, false, fb); + break; + case Matrix4: + if (verboseLogging) { + logger.info("GLES20.glUniformMatrix4fv set Matrix4." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 16; + GLES20.glUniformMatrix4fv(loc, 1, false, fb); + break; + case FloatArray: + if (verboseLogging) { + logger.info("GLES20.glUniform1fv set FloatArray." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform1fv(loc, fb.capacity(), fb); + break; + case Vector2Array: + if (verboseLogging) { + logger.info("GLES20.glUniform2fv set Vector2Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform2fv(loc, fb.capacity() / 2, fb); + break; + case Vector3Array: + if (verboseLogging) { + logger.info("GLES20.glUniform3fv set Vector3Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform3fv(loc, fb.capacity() / 3, fb); + break; + case Vector4Array: + if (verboseLogging) { + logger.info("GLES20.glUniform4fv set Vector4Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform4fv(loc, fb.capacity() / 4, fb); + break; + case Matrix4Array: + if (verboseLogging) { + logger.info("GLES20.glUniform4fv set Matrix4Array." + uniform.getName()); + } + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniformMatrix4fv(loc, fb.capacity() / 16, false, fb); + break; + case Int: + if (verboseLogging) { + logger.info("GLES20.glUniform1i set Int." + uniform.getName()); + } + Integer i = (Integer) uniform.getValue(); + GLES20.glUniform1i(loc, i.intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + } + checkGLError(); + } + + protected void updateShaderUniforms(Shader shader) { + ListMap<String, Uniform> uniforms = shader.getUniformMap(); +// for (Uniform uniform : shader.getUniforms()){ + for (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + if (uniform.isUpdateNeeded()) { + updateUniform(shader, uniform); + } + } + } + + protected void resetUniformLocations(Shader shader) { + ListMap<String, Uniform> uniforms = shader.getUniformMap(); +// for (Uniform uniform : shader.getUniforms()){ + for (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + uniform.reset(); // e.g check location again + } + } + + /* + * (Non-javadoc) + * Only used for fixed-function. Ignored. + */ + public void setLighting(LightList list) { + } + + public int convertShaderType(ShaderType type) { + switch (type) { + case Fragment: + return GLES20.GL_FRAGMENT_SHADER; + case Vertex: + return GLES20.GL_VERTEX_SHADER; +// case Geometry: +// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; + default: + throw new RuntimeException("Unrecognized shader type."); + } + } + + public void updateShaderSourceData(ShaderSource source, String language) { + int id = source.getId(); + if (id == -1) { + // create id + if (verboseLogging) { + logger.info("GLES20.glCreateShader(" + source.getType() + ")"); + } + id = GLES20.glCreateShader(convertShaderType(source.getType())); + checkGLError(); + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader."); + } + + source.setId(id); + } + + // upload shader source + // merge the defines and source code + byte[] versionData = new byte[]{};//"#version 140\n".getBytes(); +// versionData = "#define INSTANCING 1\n".getBytes(); + byte[] definesCodeData = source.getDefines().getBytes(); + byte[] sourceCodeData = source.getSource().getBytes(); + ByteBuffer codeBuf = BufferUtils.createByteBuffer(versionData.length + + definesCodeData.length + + sourceCodeData.length); + codeBuf.put(versionData); + codeBuf.put(definesCodeData); + codeBuf.put(sourceCodeData); + codeBuf.flip(); + + if (verboseLogging) { + logger.info("GLES20.glShaderSource(" + id + ")"); + } + + if (powerVr && source.getType() == ShaderType.Vertex) { + // XXX: This is to fix a bug in old PowerVR, remove + // when no longer applicable. + GLES20.glShaderSource( + id, source.getDefines() + + source.getSource()); + } else { + GLES20.glShaderSource( + id, + "precision mediump float;\n" + + source.getDefines() + + source.getSource()); + } + + checkGLError(); + + if (verboseLogging) { + logger.info("GLES20.glCompileShader(" + id + ")"); + } + + GLES20.glCompileShader(id); + + checkGLError(); + + if (verboseLogging) { + logger.info("GLES20.glGetShaderiv(" + id + ", GLES20.GL_COMPILE_STATUS)"); + } + + GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, intBuf1); + + checkGLError(); + + boolean compiledOK = intBuf1.get(0) == GLES20.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !compiledOK) { + // even if compile succeeded, check + // log for warnings + if (verboseLogging) { + logger.info("GLES20.glGetShaderiv()"); + } + GLES20.glGetShaderiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); + checkGLError(); + if (verboseLogging) { + logger.info("GLES20.glGetShaderInfoLog(" + id + ")"); + } + infoLog = GLES20.glGetShaderInfoLog(id); + logger.severe("Errooooooooooot(" + id + ")"); + } + + if (compiledOK) { + if (infoLog != null) { + logger.log(Level.INFO, "compile success: " + source.getName() + ", " + infoLog); + } else { + logger.log(Level.FINE, "compile success: " + source.getName()); + } + } else { + logger.log(Level.WARNING, "Bad compile of:\n{0}{1}", + new Object[]{source.getDefines(), source.getSource()}); + if (infoLog != null) { + throw new RendererException("compile error in:" + source + " error:" + infoLog); + } else { + throw new RendererException("compile error in:" + source + " error: <not provided>"); + } + } + + source.clearUpdateNeeded(); + // only usable if compiled + source.setUsable(compiledOK); + if (!compiledOK) { + // make sure to dispose id cause all program's + // shaders will be cleared later. + if (verboseLogging) { + logger.info("GLES20.glDeleteShader(" + id + ")"); + } + GLES20.glDeleteShader(id); + checkGLError(); + } else { + // register for cleanup since the ID is usable + objManager.registerForCleanup(source); + } + } + + public void updateShaderData(Shader shader) { + int id = shader.getId(); + boolean needRegister = false; + if (id == -1) { + // create program + + if (verboseLogging) { + logger.info("GLES20.glCreateProgram()"); + } + + id = GLES20.glCreateProgram(); + + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader program."); + } + + shader.setId(id); + needRegister = true; + } + + for (ShaderSource source : shader.getSources()) { + if (source.isUpdateNeeded()) { + updateShaderSourceData(source, shader.getLanguage()); + // shader has been compiled here + } + + if (!source.isUsable()) { + // it's useless.. just forget about everything.. + shader.setUsable(false); + shader.clearUpdateNeeded(); + return; + } + if (verboseLogging) { + logger.info("GLES20.glAttachShader(" + id + ", " + source.getId() + ")"); + } + + GLES20.glAttachShader(id, source.getId()); + } + + // link shaders to program + if (verboseLogging) { + logger.info("GLES20.glLinkProgram(" + id + ")"); + } + + GLES20.glLinkProgram(id); + + + if (verboseLogging) { + logger.info("GLES20.glGetProgramiv(" + id + ")"); + } + + GLES20.glGetProgramiv(id, GLES20.GL_LINK_STATUS, intBuf1); + + boolean linkOK = intBuf1.get(0) == GLES20.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !linkOK) { + if (verboseLogging) { + logger.info("GLES20.glGetProgramiv(" + id + ", GLES20.GL_INFO_LOG_LENGTH, buffer)"); + } + + GLES20.glGetProgramiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); + + int length = intBuf1.get(0); + if (length > 3) { + // get infos + + if (verboseLogging) { + logger.info("GLES20.glGetProgramInfoLog(" + id + ")"); + } + + infoLog = GLES20.glGetProgramInfoLog(id); + } + } + + if (linkOK) { + if (infoLog != null) { + logger.log(Level.INFO, "shader link success. \n{0}", infoLog); + } else { + logger.fine("shader link success"); + } + } else { + if (infoLog != null) { + throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog); + } else { + throw new RendererException("Shader link failure, shader:" + shader + " info: <not provided>"); + } + } + + shader.clearUpdateNeeded(); + if (!linkOK) { + // failure.. forget about everything + shader.resetSources(); + shader.setUsable(false); + deleteShader(shader); + } else { + shader.setUsable(true); + if (needRegister) { + objManager.registerForCleanup(shader); + statistics.onNewShader(); + } else { + // OpenGL spec: uniform locations may change after re-link + resetUniformLocations(shader); + } + } + } + + public void setShader(Shader shader) { + if (verboseLogging) { + logger.info("setShader(" + shader + ")"); + } + + if (shader == null) { + if (context.boundShaderProgram > 0) { + + if (verboseLogging) { + logger.info("GLES20.glUseProgram(0)"); + } + + GLES20.glUseProgram(0); + + statistics.onShaderUse(null, true); + context.boundShaderProgram = 0; + boundShader = null; + } + } else { + if (shader.isUpdateNeeded()) { + updateShaderData(shader); + } + + // NOTE: might want to check if any of the + // sources need an update? + + if (!shader.isUsable()) { + logger.warning("shader is not usable."); + return; + } + + assert shader.getId() > 0; + + updateShaderUniforms(shader); + if (context.boundShaderProgram != shader.getId()) { + if (VALIDATE_SHADER) { + // check if shader can be used + // with current state + if (verboseLogging) { + logger.info("GLES20.glValidateProgram(" + shader.getId() + ")"); + } + + GLES20.glValidateProgram(shader.getId()); + + if (verboseLogging) { + logger.info("GLES20.glGetProgramiv(" + shader.getId() + ", GLES20.GL_VALIDATE_STATUS, buffer)"); + } + + GLES20.glGetProgramiv(shader.getId(), GLES20.GL_VALIDATE_STATUS, intBuf1); + + boolean validateOK = intBuf1.get(0) == GLES20.GL_TRUE; + + if (validateOK) { + logger.fine("shader validate success"); + } else { + logger.warning("shader validate failure"); + } + } + + if (verboseLogging) { + logger.info("GLES20.glUseProgram(" + shader.getId() + ")"); + } + + GLES20.glUseProgram(shader.getId()); + + statistics.onShaderUse(shader, true); + context.boundShaderProgram = shader.getId(); + boundShader = shader; + } else { + statistics.onShaderUse(shader, false); + } + } + } + + public void deleteShaderSource(ShaderSource source) { + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + source.setUsable(false); + source.clearUpdateNeeded(); + + if (verboseLogging) { + logger.info("GLES20.glDeleteShader(" + source.getId() + ")"); + } + + GLES20.glDeleteShader(source.getId()); + source.resetObject(); + } + + public void deleteShader(Shader shader) { + if (shader.getId() == -1) { + logger.warning("Shader is not uploaded to GPU, cannot delete."); + return; + } + for (ShaderSource source : shader.getSources()) { + if (source.getId() != -1) { + + if (verboseLogging) { + logger.info("GLES20.glDetachShader(" + shader.getId() + ", " + source.getId() + ")"); + } + + GLES20.glDetachShader(shader.getId(), source.getId()); + // the next part is done by the GLObjectManager automatically +// glDeleteShader(source.getId()); + } + } + // kill all references so sources can be collected + // if needed. + shader.resetSources(); + + if (verboseLogging) { + logger.info("GLES20.glDeleteProgram(" + shader.getId() + ")"); + } + + GLES20.glDeleteProgram(shader.getId()); + + statistics.onDeleteShader(); + } + + /*********************************************************************\ + |* Framebuffers *| + \*********************************************************************/ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + logger.warning("copyFrameBuffer is not supported."); + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + logger.warning("copyFrameBuffer is not supported."); + } + /* + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst){ + if (GLContext.getCapabilities().GL_EXT_framebuffer_blit){ + int srcW = 0; + int srcH = 0; + int dstW = 0; + int dstH = 0; + int prevFBO = context.boundFBO; + + if (src != null && src.isUpdateNeeded()) + updateFrameBuffer(src); + + if (dst != null && dst.isUpdateNeeded()) + updateFrameBuffer(dst); + + if (src == null){ + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); + // srcW = viewWidth; + // srcH = viewHeight; + }else{ + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src.getId()); + srcW = src.getWidth(); + srcH = src.getHeight(); + } + if (dst == null){ + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); + // dstW = viewWidth; + // dstH = viewHeight; + }else{ + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); + dstW = dst.getWidth(); + dstH = dst.getHeight(); + } + glBlitFramebufferEXT(0, 0, srcW, srcH, + 0, 0, dstW, dstH, + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, + GL_NEAREST); + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, prevFBO); + try { + checkFrameBufferError(); + } catch (IllegalStateException ex){ + logger.log(Level.SEVERE, "Source FBO:\n{0}", src); + logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); + throw ex; + } + }else{ + throw new UnsupportedOperationException("EXT_framebuffer_blit required."); + // TODO: support non-blit copies? + } + } + */ + + private void checkFrameBufferError() { + logger.warning("checkFrameBufferError is not supported."); + } + /* + private void checkFrameBufferError() { + int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE_EXT: + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + //Choose different formats + throw new IllegalStateException("Framebuffer object format is " + + "unsupported by the video hardware."); + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + throw new IllegalStateException("Framebuffer has erronous attachment."); + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + throw new IllegalStateException("Framebuffer is missing required attachment."); + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: + throw new IllegalStateException("Framebuffer attachments must have same dimensions."); + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: + throw new IllegalStateException("Framebuffer attachments must have same formats."); + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + throw new IllegalStateException("Incomplete draw buffer."); + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + throw new IllegalStateException("Incomplete read buffer."); + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + throw new IllegalStateException("Incomplete multisample buffer."); + default: + //Programming error; will fail on all hardware + throw new IllegalStateException("Some video driver error " + + "or programming error occured. " + + "Framebuffer object status is invalid. "); + } + } + */ + + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + logger.warning("updateRenderBuffer is not supported."); + } + /* + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb){ + int id = rb.getId(); + if (id == -1){ + glGenRenderbuffersEXT(intBuf1); + id = intBuf1.get(0); + rb.setId(id); + } + + if (context.boundRB != id){ + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id); + context.boundRB = id; + } + + if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) + throw new UnsupportedOperationException("Resolution "+fb.getWidth()+ + ":"+fb.getHeight()+" is not supported."); + + if (fb.getSamples() > 0 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample){ + int samples = fb.getSamples(); + if (maxFBOSamples < samples){ + samples = maxFBOSamples; + } + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, + samples, + TextureUtil.convertTextureFormat(rb.getFormat()), + fb.getWidth(), + fb.getHeight()); + }else{ + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, + TextureUtil.convertTextureFormat(rb.getFormat()), + fb.getWidth(), + fb.getHeight()); + } + } + */ + + private int convertAttachmentSlot(int attachmentSlot) { + logger.warning("convertAttachmentSlot is not supported."); + return -1; + } + /* + private int convertAttachmentSlot(int attachmentSlot){ + // can also add support for stencil here + if (attachmentSlot == -100){ + return GL_DEPTH_ATTACHMENT_EXT; + }else if (attachmentSlot < 0 || attachmentSlot >= 16){ + throw new UnsupportedOperationException("Invalid FBO attachment slot: "+attachmentSlot); + } + + return GL_COLOR_ATTACHMENT0_EXT + attachmentSlot; + } + */ + + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { + logger.warning("updateRenderTexture is not supported."); + } + /* + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb){ + Texture tex = rb.getTexture(); + Image image = tex.getImage(); + if (image.isUpdateNeeded()) + updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels()); + + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType()), + image.getId(), + 0); + } + */ + + public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) { + logger.warning("updateFrameBufferAttachment is not supported."); + } + /* + public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb){ + boolean needAttach; + if (rb.getTexture() == null){ + // if it hasn't been created yet, then attach is required. + needAttach = rb.getId() == -1; + updateRenderBuffer(fb, rb); + }else{ + needAttach = false; + updateRenderTexture(fb, rb); + } + if (needAttach){ + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + GL_RENDERBUFFER_EXT, + rb.getId()); + } + } + */ + + public void updateFrameBuffer(FrameBuffer fb) { + logger.warning("updateFrameBuffer is not supported."); + } + /* + public void updateFrameBuffer(FrameBuffer fb) { + int id = fb.getId(); + if (id == -1){ + // create FBO + glGenFramebuffersEXT(intBuf1); + id = intBuf1.get(0); + fb.setId(id); + objManager.registerForCleanup(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id); + // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0 + context.boundDrawBuf = 0; + context.boundFBO = id; + } + + FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); + if (depthBuf != null){ + updateFrameBufferAttachment(fb, depthBuf); + } + + for (int i = 0; i < fb.getNumColorBuffers(); i++){ + FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); + updateFrameBufferAttachment(fb, colorBuf); + } + + fb.clearUpdateNeeded(); + } + */ + + public void setMainFrameBufferOverride(FrameBuffer fb){ + } + + public void setFrameBuffer(FrameBuffer fb) { + if (verboseLogging) { + logger.warning("setFrameBuffer is not supported."); + } + } + /* + public void setFrameBuffer(FrameBuffer fb) { + if (lastFb == fb) + return; + + // generate mipmaps for last FB if needed + if (lastFb != null){ + for (int i = 0; i < lastFb.getNumColorBuffers(); i++){ + RenderBuffer rb = lastFb.getColorBuffer(i); + Texture tex = rb.getTexture(); + if (tex != null + && tex.getMinFilter().usesMipMapLevels()){ + setTexture(0, rb.getTexture()); + glGenerateMipmapEXT(convertTextureType(tex.getType())); + } + } + } + + + if (fb == null){ + // unbind any fbos + if (context.boundFBO != 0){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + statistics.onFrameBufferUse(null, true); + + context.boundFBO = 0; + } + // select back buffer + if (context.boundDrawBuf != -1){ + glDrawBuffer(initialDrawBuf); + context.boundDrawBuf = -1; + } + if (context.boundReadBuf != -1){ + glReadBuffer(initialReadBuf); + context.boundReadBuf = -1; + } + + lastFb = null; + }else{ + if (fb.isUpdateNeeded()) + updateFrameBuffer(fb); + + if (context.boundFBO != fb.getId()){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb.getId()); + statistics.onFrameBufferUse(fb, true); + + // update viewport to reflect framebuffer's resolution + setViewPort(0, 0, fb.getWidth(), fb.getHeight()); + + context.boundFBO = fb.getId(); + }else{ + statistics.onFrameBufferUse(fb, false); + } + if (fb.getNumColorBuffers() == 0){ + // make sure to select NONE as draw buf + // no color buffer attached. select NONE + if (context.boundDrawBuf != -2){ + glDrawBuffer(GL_NONE); + context.boundDrawBuf = -2; + } + if (context.boundReadBuf != -2){ + glReadBuffer(GL_NONE); + context.boundReadBuf = -2; + } + }else{ + if (fb.isMultiTarget()){ + if (fb.getNumColorBuffers() > maxMRTFBOAttachs) + throw new UnsupportedOperationException("Framebuffer has more" + + " targets than are supported" + + " on the system!"); + + if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()){ + intBuf16.clear(); + for (int i = 0; i < fb.getNumColorBuffers(); i++) + intBuf16.put( GL_COLOR_ATTACHMENT0_EXT + i ); + + intBuf16.flip(); + glDrawBuffers(intBuf16); + context.boundDrawBuf = 100 + fb.getNumColorBuffers(); + } + }else{ + RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); + // select this draw buffer + if (context.boundDrawBuf != rb.getSlot()){ + glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + context.boundDrawBuf = rb.getSlot(); + } + } + } + + assert fb.getId() >= 0; + assert context.boundFBO == fb.getId(); + lastFb = fb; + } + + try { + checkFrameBufferError(); + } catch (IllegalStateException ex){ + logger.log(Level.SEVERE, "Problem FBO:\n{0}", fb); + throw ex; + } + } + */ + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + logger.warning("readFrameBuffer is not supported."); + } + /* + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf){ + if (fb != null){ + RenderBuffer rb = fb.getColorBuffer(); + if (rb == null) + throw new IllegalArgumentException("Specified framebuffer" + + " does not have a colorbuffer"); + + setFrameBuffer(fb); + if (context.boundReadBuf != rb.getSlot()){ + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + context.boundReadBuf = rb.getSlot(); + } + }else{ + setFrameBuffer(null); + } + + glReadPixels(vpX, vpY, vpW, vpH, GL_RGBA GL_BGRA, GL_UNSIGNED_BYTE, byteBuf); + } + */ + + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + logger.warning("deleteRenderBuffer is not supported."); + } + /* + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb){ + intBuf1.put(0, rb.getId()); + glDeleteRenderbuffersEXT(intBuf1); + } + */ + + public void deleteFrameBuffer(FrameBuffer fb) { + logger.warning("deleteFrameBuffer is not supported."); + } + /* + public void deleteFrameBuffer(FrameBuffer fb) { + if (fb.getId() != -1){ + if (context.boundFBO == fb.getId()){ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + context.boundFBO = 0; + } + + if (fb.getDepthBuffer() != null){ + deleteRenderBuffer(fb, fb.getDepthBuffer()); + } + if (fb.getColorBuffer() != null){ + deleteRenderBuffer(fb, fb.getColorBuffer()); + } + + intBuf1.put(0, fb.getId()); + glDeleteFramebuffersEXT(intBuf1); + fb.resetObject(); + + statistics.onDeleteFrameBuffer(); + } + } + */ + + /*********************************************************************\ + |* Textures *| + \*********************************************************************/ + private int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return GLES20.GL_TEXTURE_2D; + // case TwoDimensionalArray: + // return EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT; +// case ThreeDimensional: + // return GLES20.GL_TEXTURE_3D; + case CubeMap: + return GLES20.GL_TEXTURE_CUBE_MAP; + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return GLES20.GL_LINEAR; + case Nearest: + return GLES20.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return GLES20.GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return GLES20.GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return GLES20.GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return GLES20.GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return GLES20.GL_LINEAR; + case NearestNoMipMaps: + return GLES20.GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { +// case BorderClamp: +// return GLES20.GL_CLAMP_TO_BORDER; +// case Clamp: +// return GLES20.GL_CLAMP; + case EdgeClamp: + return GLES20.GL_CLAMP_TO_EDGE; + case Repeat: + return GLES20.GL_REPEAT; + case MirroredRepeat: + return GLES20.GL_MIRRORED_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: " + mode); + } + } + + /** + * <code>setupTextureParams</code> sets the OpenGL context texture parameters + * @param tex the Texture to set the texture parameters from + */ + private void setupTextureParams(Texture tex) { + int target = convertTextureType(tex.getType()); + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_MIN_FILTER, " + minFilter + ")"); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MIN_FILTER, minFilter); + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_MAG_FILTER, " + magFilter + ")"); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MAG_FILTER, magFilter); + + /* + if (tex.getAnisotropicFilter() > 1){ + + if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){ + glTexParameterf(target, + EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, + tex.getAnisotropicFilter()); + } + + } + */ + // repeat modes + + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: // cubemaps use 3D coords + // GL_TEXTURE_WRAP_R is not available in api 8 + //GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + case TwoDimensionalArray: + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_WRAP_T, " + convertWrapMode(tex.getWrap(WrapAxis.T))); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + + // fall down here is intentional.. +// case OneDimensional: + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + ", GLES20.GL_TEXTURE_WRAP_S, " + convertWrapMode(tex.getWrap(WrapAxis.S))); + } + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); + } + + // R to Texture compare mode +/* + if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off){ + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_MODE, GLES20.GL_COMPARE_R_TO_TEXTURE); + GLES20.glTexParameteri(target, GLES20.GL_DEPTH_TEXTURE_MODE, GLES20.GL_INTENSITY); + if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual){ + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_GEQUAL); + }else{ + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_COMPARE_FUNC, GLES20.GL_LEQUAL); + } + } + */ + } + + /** + * <code>updateTexImageData</code> activates and binds the texture + * @param img + * @param type + * @param mips + */ + public void updateTexImageData(Image img, Texture.Type type, boolean mips) { + int texId = img.getId(); + if (texId == -1) { + // create texture + if (verboseLogging) { + logger.info("GLES20.glGenTexture(1, buffer)"); + } + + GLES20.glGenTextures(1, intBuf1); + texId = intBuf1.get(0); + img.setId(texId); + objManager.registerForCleanup(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type); + if (context.boundTextures[0] != img) { + if (context.boundTextureUnit != 0) { + if (verboseLogging) { + logger.info("GLES20.glActiveTexture(GLES20.GL_TEXTURE0)"); + } + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + + if (verboseLogging) { + logger.info("GLES20.glBindTexture(" + target + ", " + texId + ")"); + } + + GLES20.glBindTexture(target, texId); + context.boundTextures[0] = img; + } + + + if (target == GLES20.GL_TEXTURE_CUBE_MAP) { + // Upload a cube map / sky box + @SuppressWarnings("unchecked") + List<AndroidImageInfo> bmps = (List<AndroidImageInfo>) img.getEfficentData(); + if (bmps != null) { + // Native android bitmap + if (bmps.size() != 6) { + throw new UnsupportedOperationException("Invalid texture: " + img + + "Cubemap textures must contain 6 data units."); + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTextureBitmap(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, bmps.get(i).getBitmap(), false, powerOf2); + } + } else { + // Standard jme3 image data + List<ByteBuffer> data = img.getData(); + if (data.size() != 6) { + logger.log(Level.WARNING, "Invalid texture: {0}\n" + + "Cubemap textures must contain 6 data units.", img); + return; + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTexture(img, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, tdc, false, powerOf2); + } + } + } else { + TextureUtil.uploadTexture(img, target, 0, 0, tdc, false, powerOf2); + + if (verboseLogging) { + logger.info("GLES20.glTexParameteri(" + target + "GLES11.GL_GENERATE_MIMAP, GLES20.GL_TRUE)"); + } + + if (!img.hasMipmaps() && mips) { + // No pregenerated mips available, + // generate from base level if required + if (verboseLogging) { + logger.info("GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D)"); + } + GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); + } + } + + img.clearUpdateNeeded(); + } + + public void setTexture(int unit, Texture tex) { + Image image = tex.getImage(); + if (image.isUpdateNeeded()) { + /* + Bitmap bmp = (Bitmap)image.getEfficentData(); + if (bmp != null) + { + // Check if the bitmap got recycled, can happen after wakeup/restart + if ( bmp.isRecycled() ) + { + // We need to reload the bitmap + Texture textureReloaded = JmeSystem.newAssetManager().loadTexture((TextureKey)tex.getKey()); + image.setEfficentData( textureReloaded.getImage().getEfficentData()); + } + } + */ + updateTexImageData(image, tex.getType(), tex.getMinFilter().usesMipMapLevels()); + } + + int texId = image.getId(); + assert texId != -1; + + if (texId == -1) { + logger.warning("error: texture image has -1 id"); + } + + Image[] textures = context.boundTextures; + + int type = convertTextureType(tex.getType()); + if (!context.textureIndexList.moveToNew(unit)) { +// if (context.boundTextureUnit != unit){ +// glActiveTexture(GL_TEXTURE0 + unit); +// context.boundTextureUnit = unit; +// } +// glEnable(type); + } + + if (textures[unit] != image) { + if (context.boundTextureUnit != unit) { + if (verboseLogging) { + logger.info("GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + " + unit + ")"); + } + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + if (verboseLogging) { + logger.info("GLES20.glBindTexture(" + type + ", " + texId + ")"); + } + + GLES20.glBindTexture(type, texId); + textures[unit] = image; + + statistics.onTextureUse(tex.getImage(), true); + } else { + statistics.onTextureUse(tex.getImage(), false); + } + + setupTextureParams(tex); + } + + public void clearTextureUnits() { + IDList textureList = context.textureIndexList; + Image[] textures = context.boundTextures; + for (int i = 0; i < textureList.oldLen; i++) { + int idx = textureList.oldList[i]; +// if (context.boundTextureUnit != idx){ +// glActiveTexture(GL_TEXTURE0 + idx); +// context.boundTextureUnit = idx; +// } +// glDisable(convertTextureType(textures[idx].getType())); + textures[idx] = null; + } + context.textureIndexList.copyNewToOld(); + } + + public void deleteImage(Image image) { + int texId = image.getId(); + if (texId != -1) { + intBuf1.put(0, texId); + intBuf1.position(0).limit(1); + + if (verboseLogging) { + logger.info("GLES20.glDeleteTexture(1, buffer)"); + } + + GLES20.glDeleteTextures(1, intBuf1); + image.resetObject(); + + statistics.onDeleteTexture(); + } + } + + /*********************************************************************\ + |* Vertex Buffers and Attributes *| + \*********************************************************************/ + private int convertUsage(Usage usage) { + switch (usage) { + case Static: + return GLES20.GL_STATIC_DRAW; + case Dynamic: + return GLES20.GL_DYNAMIC_DRAW; + case Stream: + return GLES20.GL_STREAM_DRAW; + default: + throw new RuntimeException("Unknown usage type."); + } + } + + private int convertFormat(Format format) { + switch (format) { + case Byte: + return GLES20.GL_BYTE; + case UnsignedByte: + return GLES20.GL_UNSIGNED_BYTE; + case Short: + return GLES20.GL_SHORT; + case UnsignedShort: + return GLES20.GL_UNSIGNED_SHORT; + case Int: + return GLES20.GL_INT; + case UnsignedInt: + return GLES20.GL_UNSIGNED_INT; + /* + case Half: + return NVHalfFloat.GL_HALF_FLOAT_NV; + // return ARBHalfFloatVertex.GL_HALF_FLOAT; + */ + case Float: + return GLES20.GL_FLOAT; +// case Double: +// return GLES20.GL_DOUBLE; + default: + throw new RuntimeException("Unknown buffer format."); + + } + } + + public void updateBufferData(VertexBuffer vb) { + + if (verboseLogging) { + logger.info("updateBufferData(" + vb + ")"); + } + + int bufId = vb.getId(); + boolean created = false; + if (bufId == -1) { + // create buffer + + if (verboseLogging) { + logger.info("GLES20.glGenBuffers(" + 1 + ", buffer)"); + } + + GLES20.glGenBuffers(1, intBuf1); + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerForCleanup(vb); + + created = true; + } + + // bind buffer + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = GLES20.GL_ELEMENT_ARRAY_BUFFER; + + if (verboseLogging) { + logger.info("vb.getBufferType() == VertexBuffer.Type.Index"); + } + + if (context.boundElementArrayVBO != bufId) { + + if (verboseLogging) { + logger.info("GLES20.glBindBuffer(" + target + ", " + bufId + ")"); + } + + GLES20.glBindBuffer(target, bufId); + context.boundElementArrayVBO = bufId; + } + } else { + if (verboseLogging) { + logger.info("vb.getBufferType() != VertexBuffer.Type.Index"); + } + + target = GLES20.GL_ARRAY_BUFFER; + + if (context.boundArrayVBO != bufId) { + + if (verboseLogging) { + logger.info("GLES20.glBindBuffer(" + target + ", " + bufId + ")"); + } + + GLES20.glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + vb.getData().clear(); + + if (created || vb.hasDataSizeChanged()) { + // upload data based on format + int size = vb.getData().capacity() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); + break; + // case Half: + case Short: + case UnsignedShort: + + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); + break; + case Int: + case UnsignedInt: + + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (IntBuffer) vb.getData(), usage); + break; + case Float: + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); + break; + case Double: + if (verboseLogging) { + logger.info("GLES20.glBufferData(" + target + ", " + size + ", (data), " + usage + ")"); + } + + GLES20.glBufferData(target, size, (DoubleBuffer) vb.getData(), usage); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } else { + int size = vb.getData().capacity() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); + break; + case Short: + case UnsignedShort: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); + break; + case Int: + case UnsignedInt: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); + break; + case Float: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); + break; + case Double: + if (verboseLogging) { + logger.info("GLES20.glBufferSubData(" + target + ", 0, " + size + ", (data))"); + } + + GLES20.glBufferSubData(target, 0, size, (DoubleBuffer) vb.getData()); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } +// }else{ +// if (created || vb.hasDataSizeChanged()){ +// glBufferData(target, vb.getData().capacity() * vb.getFormat().getComponentSize(), usage); +// } +// +// ByteBuffer buf = glMapBuffer(target, +// GL_WRITE_ONLY, +// vb.getMappedData()); +// +// if (buf != vb.getMappedData()){ +// buf = buf.order(ByteOrder.nativeOrder()); +// vb.setMappedData(buf); +// } +// +// buf.clear(); +// +// switch (vb.getFormat()){ +// case Byte: +// case UnsignedByte: +// buf.put( (ByteBuffer) vb.getData() ); +// break; +// case Short: +// case UnsignedShort: +// buf.asShortBuffer().put( (ShortBuffer) vb.getData() ); +// break; +// case Int: +// case UnsignedInt: +// buf.asIntBuffer().put( (IntBuffer) vb.getData() ); +// break; +// case Float: +// buf.asFloatBuffer().put( (FloatBuffer) vb.getData() ); +// break; +// case Double: +// break; +// default: +// throw new RuntimeException("Unknown buffer format."); +// } +// +// glUnmapBuffer(target); +// } + + vb.clearUpdateNeeded(); + } + + public void deleteBuffer(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + intBuf1.put(0, bufId); + intBuf1.position(0).limit(1); + if (verboseLogging) { + logger.info("GLES20.glDeleteBuffers(1, buffer)"); + } + + GLES20.glDeleteBuffers(1, intBuf1); + vb.resetObject(); + } + } + + public void clearVertexAttribs() { + IDList attribList = context.attribIndexList; + for (int i = 0; i < attribList.oldLen; i++) { + int idx = attribList.oldList[i]; + + if (verboseLogging) { + logger.info("GLES20.glDisableVertexAttribArray(" + idx + ")"); + } + + GLES20.glDisableVertexAttribArray(idx); + context.boundAttribs[idx] = null; + } + context.attribIndexList.copyNewToOld(); + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (verboseLogging) { + logger.info("setVertexAttrib(" + vb + ", " + idb + ")"); + } + + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + int programId = context.boundShaderProgram; + if (programId > 0) { + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + + if (verboseLogging) { + logger.warning("location is invalid for attrib: [" + vb.getBufferType().name() + "]"); + } + + return; // not defined + } + + if (loc == -2) { +// stringBuf.setLength(0); +// stringBuf.append("in").append(vb.getBufferType().name()).append('\0'); +// updateNameBuffer(); + + String attributeName = "in" + vb.getBufferType().name(); + + if (verboseLogging) { + logger.info("GLES20.glGetAttribLocation(" + programId + ", " + attributeName + ")"); + } + + loc = GLES20.glGetAttribLocation(programId, attributeName); + + // not really the name of it in the shader (inPosition\0) but + // the internal name of the enum (Position). + if (loc < 0) { + attrib.setLocation(-1); + + if (verboseLogging) { + logger.warning("attribute is invalid in shader: [" + vb.getBufferType().name() + "]"); + } + + return; // not available in shader. + } else { + attrib.setLocation(loc); + } + } + + VertexBuffer[] attribs = context.boundAttribs; + if (!context.attribIndexList.moveToNew(loc)) { + if (verboseLogging) { + logger.info("GLES20.glEnableVertexAttribArray(" + loc + ")"); + } + + GLES20.glEnableVertexAttribArray(loc); + //System.out.println("Enabled ATTRIB IDX: "+loc); + } + if (attribs[loc] != vb) { + // NOTE: Use id from interleaved buffer if specified + int bufId = idb != null ? idb.getId() : vb.getId(); + assert bufId != -1; + + if (bufId == -1) { + logger.warning("invalid buffer id"); + } + + if (context.boundArrayVBO != bufId) { + if (verboseLogging) { + logger.info("GLES20.glBindBuffer(" + GLES20.GL_ARRAY_BUFFER + ", " + bufId + ")"); + } + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + } + + vb.getData().clear(); + + if (verboseLogging) { + logger.info("GLES20.glVertexAttribPointer(" + + "location=" + loc + ", " + + "numComponents=" + vb.getNumComponents() + ", " + + "format=" + vb.getFormat() + ", " + + "isNormalized=" + vb.isNormalized() + ", " + + "stride=" + vb.getStride() + ", " + + "data.capacity=" + vb.getData().capacity() + ")"); + } + + Android22Workaround.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + 0); + + attribs[loc] = vb; + } + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { + /* if (count > 1){ + ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0, + vertCount, count); + }else{*/ + if (verboseLogging) { + logger.info("GLES20.glDrawArrays(" + vertCount + ")"); + } + + GLES20.glDrawArrays(convertElementMode(mode), 0, vertCount); + /* + }*/ + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + + if (verboseLogging) { + logger.info("drawTriangleList(" + count + ")"); + } + + if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); + } + + if (indexBuf.isUpdateNeeded()) { + if (verboseLogging) { + logger.info("updateBufferData for indexBuf."); + } + updateBufferData(indexBuf); + } + + int bufId = indexBuf.getId(); + assert bufId != -1; + + if (bufId == -1) { + logger.info("invalid buffer id!"); + } + + if (context.boundElementArrayVBO != bufId) { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, {0})", bufId); + } + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufId); + context.boundElementArrayVBO = bufId; + } + + int vertCount = mesh.getVertexCount(); + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + + Buffer indexData = indexBuf.getData(); + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertFormat(indexBuf.getFormat()); + int elSize = indexBuf.getFormat().getComponentSize(); + int listStart = modeStart[0]; + int stripStart = modeStart[1]; + int fanStart = modeStart[2]; + int curOffset = 0; + for (int i = 0; i < elementLengths.length; i++) { + if (i == stripStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } else if (i == fanStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } + int elementLength = elementLengths[i]; + + if (useInstancing) { + //ARBDrawInstanced. + throw new IllegalArgumentException("instancing is not supported."); + /* + GLES20.glDrawElementsInstancedARB(elMode, + elementLength, + fmt, + curOffset, + count); + */ + } else { + indexBuf.getData().position(curOffset); + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(): {0}, {1}", new Object[]{elementLength, curOffset}); + } + + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + /* + glDrawRangeElements(elMode, + 0, + vertCount, + elementLength, + fmt, + curOffset); + */ + } + + curOffset += elementLength * elSize; + } + } else { + if (useInstancing) { + throw new IllegalArgumentException("instancing is not supported."); + //ARBDrawInstanced. +/* + GLES20.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertFormat(indexBuf.getFormat()), + 0, + count); + */ + } else { + indexData.clear(); + + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(), indexBuf.capacity ({0}), vertCount ({1})", new Object[]{indexBuf.getData().capacity(), vertCount}); + } + + GLES11.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertFormat(indexBuf.getFormat()), + 0); + } + } + } + + /*********************************************************************\ + |* Render Calls *| + \*********************************************************************/ + public int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return GLES20.GL_POINTS; + case Lines: + return GLES20.GL_LINES; + case LineLoop: + return GLES20.GL_LINE_LOOP; + case LineStrip: + return GLES20.GL_LINE_STRIP; + case Triangles: + return GLES20.GL_TRIANGLES; + case TriangleFan: + return GLES20.GL_TRIANGLE_FAN; + case TriangleStrip: + return GLES20.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + public void updateVertexArray(Mesh mesh) { + logger.log(Level.INFO, "updateVertexArray({0})", mesh); + int id = mesh.getId(); + /* + if (id == -1){ + IntBuffer temp = intBuf1; + // ARBVertexArrayObject.glGenVertexArrays(temp); + GLES20.glGenVertexArrays(temp); + id = temp.get(0); + mesh.setId(id); + } + + if (context.boundVertexArray != id){ + // ARBVertexArrayObject.glBindVertexArray(id); + GLES20.glBindVertexArray(id); + context.boundVertexArray = id; + } + */ + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + + for (VertexBuffer vb : mesh.getBufferList().getArray()){ + + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + /** + * renderMeshVertexArray renders a mesh using vertex arrays + * @param mesh + * @param lod + * @param count + */ + private void renderMeshVertexArray(Mesh mesh, int lod, int count) { + if (verboseLogging) { + logger.info("renderMeshVertexArray"); + } + + // IntMap<VertexBuffer> buffers = mesh.getBuffers(); + for (VertexBuffer vb : mesh.getBufferList().getArray()){ + + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib_Array(vb); + } else { + // interleaved + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + setVertexAttrib_Array(vb, interleavedData); + } + } + + VertexBuffer indices = null; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index);//buffers.get(Type.Index.ordinal()); + } + if (indices != null) { + drawTriangleList_Array(indices, mesh, count); + } else { + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glDrawArrays({0}, {1}, {2})", + new Object[]{mesh.getMode(), 0, mesh.getVertexCount()}); + } + + GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + if (verboseLogging) { + logger.log(Level.INFO, "renderMeshDefault({0}, {1}, {2})", + new Object[]{mesh, lod, count}); + } + VertexBuffer indices = null; + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + //IntMap<VertexBuffer> buffers = mesh.getBuffers(); ; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal()); + } + for (VertexBuffer vb : mesh.getBufferList().getArray()){ + + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { +// throw new UnsupportedOperationException("Cannot render without index buffer"); + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glDrawArrays({0}, 0, {1})", + new Object[]{convertElementMode(mesh.getMode()), mesh.getVertexCount()}); + } + + GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (context.pointSize != mesh.getPointSize()) { + + if (verboseLogging) { + logger.log(Level.INFO, "GLES10.glPointSize({0})", mesh.getPointSize()); + } + + GLES10.glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glLineWidth({0})", mesh.getLineWidth()); + } + + GLES20.glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + + if (useVBO) { + if (verboseLogging) { + logger.info("RENDERING A MESH USING VertexBufferObject"); + } + + renderMeshDefault(mesh, lod, count); + } else { + if (verboseLogging) { + logger.info("RENDERING A MESH USING VertexArray"); + } + + renderMeshVertexArray(mesh, lod, count); + } + +// } + } + + /** + * drawTriangleList_Array uses Vertex Array + * @param indexBuf + * @param mesh + * @param count + */ + public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int count) { + if (verboseLogging) { + logger.log(Level.INFO, "drawTriangleList_Array(Count = {0})", count); + } + + if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); + } + + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + if (useInstancing) { + throw new IllegalArgumentException("Caps.MeshInstancing is not supported."); + } + + int vertCount = mesh.getVertexCount(); + Buffer indexData = indexBuf.getData(); + indexData.clear(); + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertFormat(indexBuf.getFormat()); + int elSize = indexBuf.getFormat().getComponentSize(); + int listStart = modeStart[0]; + int stripStart = modeStart[1]; + int fanStart = modeStart[2]; + int curOffset = 0; + for (int i = 0; i < elementLengths.length; i++) { + if (i == stripStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } else if (i == fanStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } + int elementLength = elementLengths[i]; + + indexBuf.getData().position(curOffset); + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(): {0}, {1}", new Object[]{elementLength, curOffset}); + } + + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + + curOffset += elementLength * elSize; + } + } else { + if (verboseLogging) { + logger.log(Level.INFO, "glDrawElements(), indexBuf.capacity ({0}), vertCount ({1})", new Object[]{indexBuf.getData().capacity(), vertCount}); + } + + GLES20.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().capacity(), + convertFormat(indexBuf.getFormat()), + indexBuf.getData()); + } + } + + /** + * setVertexAttrib_Array uses Vertex Array + * @param vb + * @param idb + */ + public void setVertexAttrib_Array(VertexBuffer vb, VertexBuffer idb) { + if (verboseLogging) { + logger.log(Level.INFO, "setVertexAttrib_Array({0}, {1})", new Object[]{vb, idb}); + } + + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + // Get shader + int programId = context.boundShaderProgram; + if (programId > 0) { + VertexBuffer[] attribs = context.boundAttribs; + + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + //throw new IllegalArgumentException("Location is invalid for attrib: [" + vb.getBufferType().name() + "]"); + if (verboseLogging) { + logger.log(Level.WARNING, "attribute is invalid in shader: [{0}]", vb.getBufferType().name()); + } + return; + } else if (loc == -2) { + String attributeName = "in" + vb.getBufferType().name(); + + if (verboseLogging) { + logger.log(Level.INFO, "GLES20.glGetAttribLocation({0}, {1})", new Object[]{programId, attributeName}); + } + + loc = GLES20.glGetAttribLocation(programId, attributeName); + if (loc < 0) { + attrib.setLocation(-1); + if (verboseLogging) { + logger.log(Level.WARNING, "attribute is invalid in shader: [{0}]", vb.getBufferType().name()); + } + return; // not available in shader. + } else { + attrib.setLocation(loc); + } + + } // if (loc == -2) + + if ((attribs[loc] != vb) || vb.isUpdateNeeded()) { + // NOTE: Use data from interleaved buffer if specified + VertexBuffer avb = idb != null ? idb : vb; + avb.getData().clear(); + avb.getData().position(vb.getOffset()); + + if (verboseLogging) { + logger.log(Level.INFO, + "GLES20.glVertexAttribPointer(" + + "location={0}, " + + "numComponents={1}, " + + "format={2}, " + + "isNormalized={3}, " + + "stride={4}, " + + "data.capacity={5})", + new Object[]{loc, vb.getNumComponents(), + vb.getFormat(), + vb.isNormalized(), + vb.getStride(), + avb.getData().capacity()}); + } + + + // Upload attribute data + GLES20.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + avb.getData()); + checkGLError(); + + GLES20.glEnableVertexAttribArray(loc); + + attribs[loc] = vb; + } // if (attribs[loc] != vb) + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + /** + * setVertexAttrib_Array uses Vertex Array + * @param vb + */ + public void setVertexAttrib_Array(VertexBuffer vb) { + setVertexAttrib_Array(vb, null); + } + + public void setAlphaToCoverage(boolean value) { + if (value) { + GLES20.glEnable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + GLES20.glDisable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); + } + } + + @Override + public void invalidateState() { + context.reset(); + boundShader = null; + lastFb = null; + } +} diff --git a/engine/src/android/com/jme3/renderer/android/TextureUtil.java b/engine/src/android/com/jme3/renderer/android/TextureUtil.java new file mode 100644 index 0000000..53b96b4 --- /dev/null +++ b/engine/src/android/com/jme3/renderer/android/TextureUtil.java @@ -0,0 +1,297 @@ +package com.jme3.renderer.android; + +import android.graphics.Bitmap; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; +import javax.microedition.khronos.opengles.GL10; + +public class TextureUtil { + + public static int convertTextureFormat(Format fmt){ + switch (fmt){ + case Alpha16: + case Alpha8: + return GL10.GL_ALPHA; + case Luminance8Alpha8: + case Luminance16Alpha16: + return GL10.GL_LUMINANCE_ALPHA; + case Luminance8: + case Luminance16: + return GL10.GL_LUMINANCE; + case RGB10: + case RGB16: + case BGR8: + case RGB8: + case RGB565: + return GL10.GL_RGB; + case RGB5A1: + case RGBA16: + case RGBA8: + return GL10.GL_RGBA; + + case Depth: + return GLES20.GL_DEPTH_COMPONENT; + case Depth16: + return GLES20.GL_DEPTH_COMPONENT16; + case Depth24: + case Depth32: + case Depth32F: + throw new UnsupportedOperationException("Unsupported depth format: " + fmt); + + case DXT1A: + throw new UnsupportedOperationException("Unsupported format: " + fmt); + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + } + + private static void buildMipmap(Bitmap bitmap) { + int level = 0; + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + while (height >= 1 || width >= 1) { + //First of all, generate the texture from our bitmap and set it to the according level + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0); + + if (height == 1 || width == 1) { + break; + } + + //Increase the mipmap level + level++; + + height /= 2; + width /= 2; + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); + + bitmap.recycle(); + bitmap = bitmap2; + } + } + + /** + * <code>uploadTextureBitmap</code> uploads a native android bitmap + * @param target + * @param bitmap + * @param generateMips + * @param powerOf2 + */ + public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean generateMips, boolean powerOf2) + { + if (!powerOf2) + { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) + { + // scale to power of two + width = FastMath.nearestPowerOfTwo(width); + height = FastMath.nearestPowerOfTwo(height); + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); + bitmap.recycle(); + bitmap = bitmap2; + } + } + + if (generateMips) + { + buildMipmap(bitmap); + } + else + { + GLUtils.texImage2D(target, 0, bitmap, 0); + //bitmap.recycle(); + } + } + + public static void uploadTexture( + Image img, + int target, + int index, + int border, + boolean tdc, + boolean generateMips, + boolean powerOf2){ + + if (img.getEfficentData() instanceof AndroidImageInfo){ + // If image was loaded from asset manager, use fast path + AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); + uploadTextureBitmap(target, imageInfo.getBitmap(), generateMips, powerOf2); + return; + } + + // Otherwise upload image directly. + // Prefer to only use power of 2 textures here to avoid errors. + + Image.Format fmt = img.getFormat(); + ByteBuffer data; + if (index >= 0 || img.getData() != null && img.getData().size() > 0){ + data = img.getData(index); + }else{ + data = null; + } + + int width = img.getWidth(); + int height = img.getHeight(); + int depth = img.getDepth(); + + boolean compress = false; + int internalFormat = -1; + int format = -1; + int dataType = -1; + + switch (fmt){ + case Alpha16: + case Alpha8: + format = GLES20.GL_ALPHA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance8: + format = GLES20.GL_LUMINANCE; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance8Alpha8: + format = GLES20.GL_LUMINANCE_ALPHA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance16Alpha16: + format = GLES20.GL_LUMINANCE_ALPHA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Luminance16: + format = GLES20.GL_LUMINANCE; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGB565: + format = GLES20.GL_RGB; + internalFormat = GLES20.GL_RGB565; + dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5; + break; + case ARGB4444: + format = GLES20.GL_RGBA; + dataType = GLES20.GL_UNSIGNED_SHORT_4_4_4_4; + break; + case RGB10: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGB16: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGB5A1: + format = GLES20.GL_RGBA; + internalFormat = GLES20.GL_RGB5_A1; + dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1; + break; + case RGB8: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case BGR8: + format = GLES20.GL_RGB; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGBA16: + format = GLES20.GL_RGBA; + internalFormat = GLES20.GL_RGBA4; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case RGBA8: + format = GLES20.GL_RGBA; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case DXT1A: + format = GLES20.GL_COMPRESSED_TEXTURE_FORMATS; + dataType = GLES20.GL_UNSIGNED_BYTE; + case Depth: + format = GLES20.GL_DEPTH_COMPONENT; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Depth16: + format = GLES20.GL_DEPTH_COMPONENT; + internalFormat = GLES20.GL_DEPTH_COMPONENT16; + dataType = GLES20.GL_UNSIGNED_BYTE; + break; + case Depth24: + case Depth32: + case Depth32F: + throw new UnsupportedOperationException("Unsupported depth format: " + fmt); + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + + if (internalFormat == -1) + { + internalFormat = format; + } + + if (data != null) + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); + + int[] mipSizes = img.getMipMapSizes(); + int pos = 0; + if (mipSizes == null){ + if (data != null) + mipSizes = new int[]{ data.capacity() }; + else + mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; + } + + // XXX: might want to change that when support + // of more than paletted compressions is added.. + if (compress){ + data.clear(); + GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, + 1 - mipSizes.length, + format, + width, + height, + 0, + data.capacity(), + data); + return; + } + + for (int i = 0; i < mipSizes.length; i++){ + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + int mipDepth = Math.max(1, depth >> i); + + if (data != null){ + data.position(pos); + data.limit(pos + mipSizes[i]); + } + + if (compress && data != null){ + GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, + i, + format, + mipWidth, + mipHeight, + 0, + data.remaining(), + data); + }else{ + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, + i, + internalFormat, + mipWidth, + mipHeight, + 0, + format, + dataType, + data); + } + + pos += mipSizes[i]; + } + } + +} diff --git a/engine/src/android/com/jme3/system/android/AndroidConfigChooser.java b/engine/src/android/com/jme3/system/android/AndroidConfigChooser.java new file mode 100644 index 0000000..e55fa55 --- /dev/null +++ b/engine/src/android/com/jme3/system/android/AndroidConfigChooser.java @@ -0,0 +1,362 @@ +package com.jme3.system.android; + +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView.EGLConfigChooser; +import java.util.logging.Logger; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * AndroidConfigChooser is used to determine the best suited EGL Config + * @author larynx + * + */ +public class AndroidConfigChooser implements EGLConfigChooser +{ + private static final Logger logger = Logger.getLogger(AndroidConfigChooser.class.getName()); + + protected int clientOpenGLESVersion = 0; + protected EGLConfig bestConfig = null; + protected EGLConfig fastestConfig = null; + protected EGLConfig choosenConfig = null; + protected ConfigType type; + protected int pixelFormat; + + protected boolean verbose = false; + + private final static int EGL_OPENGL_ES2_BIT = 4; + + public enum ConfigType + { + /** + * RGB565, 0 alpha, 16 depth, 0 stencil + */ + FASTEST, + /** + * RGB???, 0 alpha, >=16 depth, 0 stencil + */ + BEST, + /** + * Turn off config chooser and use hardcoded + * setEGLContextClientVersion(2); + * setEGLConfigChooser(5, 6, 5, 0, 16, 0); + */ + LEGACY + } + + public AndroidConfigChooser(ConfigType type, boolean verbose) + { + this.type = type; + this.verbose = verbose; + } + + /** + * Gets called by the GLSurfaceView class to return the best config + */ + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) + { + logger.info("GLSurfaceView asks for egl config, returning: "); + logEGLConfig(choosenConfig, display, egl); + return choosenConfig; + } + + /** + * findConfig is used to locate the best config and init the chooser with + * @param egl + * @param display + * @return true if successfull, false if no config was found + */ + public boolean findConfig(EGL10 egl, EGLDisplay display) + { + + if (type == ConfigType.BEST) + { + ComponentSizeChooser compChooser = new ComponentSizeChooser(8, 8, 8, 8, 32, 0); + choosenConfig = compChooser.chooseConfig(egl, display); + + if (choosenConfig == null) + { + compChooser = new ComponentSizeChooser(8, 8, 8, 0, 32, 0); + choosenConfig = compChooser.chooseConfig(egl, display); + if (choosenConfig == null) + { + compChooser = new ComponentSizeChooser(8, 8, 8, 8, 16, 0); + choosenConfig = compChooser.chooseConfig(egl, display); + if (choosenConfig == null) + { + compChooser = new ComponentSizeChooser(8, 8, 8, 0, 16, 0); + choosenConfig = compChooser.chooseConfig(egl, display); + } + } + } + + logger.info("JME3 using best EGL configuration available here: "); + } + else + { + ComponentSizeChooser compChooser = new ComponentSizeChooser(5, 6, 5, 0, 16, 0); + choosenConfig = compChooser.chooseConfig(egl, display); + logger.info("JME3 using fastest EGL configuration available here: "); + } + + if (choosenConfig != null) + { + logger.info("JME3 using choosen config: "); + logEGLConfig(choosenConfig, display, egl); + pixelFormat = getPixelFormat(choosenConfig, display, egl); + clientOpenGLESVersion = getOpenGLVersion(choosenConfig, display, egl); + return true; + } + else + { + logger.severe("###ERROR### Unable to get a valid OpenGL ES 2.0 config, nether Fastest nor Best found! Bug. Please report this."); + clientOpenGLESVersion = 1; + pixelFormat = PixelFormat.UNKNOWN; + return false; + } + } + + + private int getPixelFormat(EGLConfig conf, EGLDisplay display, EGL10 egl) + { + int[] value = new int[1]; + int result = PixelFormat.RGB_565; + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RED_SIZE, value); + if (value[0] == 8) + { + result = PixelFormat.RGBA_8888; + /* + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_ALPHA_SIZE, value); + if (value[0] == 8) + { + result = PixelFormat.RGBA_8888; + } + else + { + result = PixelFormat.RGB_888; + }*/ + } + + if (verbose) + { + logger.info("Using PixelFormat " + result); + } + + //return result; TODO Test pixelformat + return PixelFormat.TRANSPARENT; + } + + private int getOpenGLVersion(EGLConfig conf, EGLDisplay display, EGL10 egl) + { + int[] value = new int[1]; + int result = 1; + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RENDERABLE_TYPE, value); + // Check if conf is OpenGL ES 2.0 + if ((value[0] & EGL_OPENGL_ES2_BIT) != 0) + { + result = 2; + } + + return result; + } + + /** + * log output with egl config details + * @param conf + * @param display + * @param egl + */ + public void logEGLConfig(EGLConfig conf, EGLDisplay display, EGL10 egl) + { + int[] value = new int[1]; + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RED_SIZE, value); + logger.info(String.format("EGL_RED_SIZE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_GREEN_SIZE, value); + logger.info(String.format("EGL_GREEN_SIZE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_BLUE_SIZE, value); + logger.info(String.format("EGL_BLUE_SIZE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_ALPHA_SIZE, value); + logger.info(String.format("EGL_ALPHA_SIZE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_DEPTH_SIZE, value); + logger.info(String.format("EGL_DEPTH_SIZE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_STENCIL_SIZE, value); + logger.info(String.format("EGL_STENCIL_SIZE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_RENDERABLE_TYPE, value); + logger.info(String.format("EGL_RENDERABLE_TYPE = %d", value[0] ) ); + + egl.eglGetConfigAttrib(display, conf, EGL10.EGL_SURFACE_TYPE, value); + logger.info(String.format("EGL_SURFACE_TYPE = %d", value[0] ) ); + } + + public int getClientOpenGLESVersion() + { + return clientOpenGLESVersion; + } + + public void setClientOpenGLESVersion(int clientOpenGLESVersion) + { + this.clientOpenGLESVersion = clientOpenGLESVersion; + } + + public int getPixelFormat() + { + return pixelFormat; + } + + + + private abstract class BaseConfigChooser implements EGLConfigChooser + { + private boolean bClientOpenGLESVersionSet; + + public BaseConfigChooser(int[] configSpec) + { + bClientOpenGLESVersionSet = false; + mConfigSpec = filterConfigSpec(configSpec); + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) + { + int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) + { + //throw new IllegalArgumentException("No configs match configSpec"); + + return null; + } + + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + EGLConfig config = chooseConfig(egl, display, configs); + //if (config == null) { + // throw new IllegalArgumentException("No config chosen"); + //} + return config; + } + + abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); + + protected int[] mConfigSpec; + + private int[] filterConfigSpec(int[] configSpec) + { + if (bClientOpenGLESVersionSet == true) { + return configSpec; + } + /* We know none of the subclasses define EGL_RENDERABLE_TYPE. + * And we know the configSpec is well formed. + */ + int len = configSpec.length; + int[] newConfigSpec = new int[len + 2]; + System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); + newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; + newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ + newConfigSpec[len+1] = EGL10.EGL_NONE; + + bClientOpenGLESVersionSet = true; + + return newConfigSpec; + } + } + + /** + * Choose a configuration with exactly the specified r,g,b,a sizes, + * and at least the specified depth and stencil sizes. + */ + private class ComponentSizeChooser extends BaseConfigChooser + { + public ComponentSizeChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) + { + super(new int[] { + EGL10.EGL_RED_SIZE, redSize, + EGL10.EGL_GREEN_SIZE, greenSize, + EGL10.EGL_BLUE_SIZE, blueSize, + EGL10.EGL_ALPHA_SIZE, alphaSize, + EGL10.EGL_DEPTH_SIZE, depthSize, + EGL10.EGL_STENCIL_SIZE, stencilSize, + EGL10.EGL_NONE}); + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) + { + for (EGLConfig config : configs) + { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + if ((d >= mDepthSize) && (s >= mStencilSize)) + { + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + if ((r == mRedSize) && (g == mGreenSize) + && (b == mBlueSize) && (a == mAlphaSize)) { + return config; + } + } + } + return null; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) + { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) + { + return mValue[0]; + } + return defaultValue; + } + + private int[] mValue; + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + } + + + + +} diff --git a/engine/src/android/com/jme3/system/android/AndroidTimer.java b/engine/src/android/com/jme3/system/android/AndroidTimer.java new file mode 100644 index 0000000..4cabc33 --- /dev/null +++ b/engine/src/android/com/jme3/system/android/AndroidTimer.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2003-2009 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 com.jme3.system.Timer; + +/** + * <code>AndroidTimer</code> is a System.nanoTime implementation of <code>Timer</code>. + */ +public class AndroidTimer extends Timer { + + //private static final long TIMER_RESOLUTION = 1000L; + //private static final float INVERSE_TIMER_RESOLUTION = 1f/1000L; + private static final long TIMER_RESOLUTION = 1000000000L; + private static final float INVERSE_TIMER_RESOLUTION = 1f/1000000000L; + + private long startTime; + private long previousTime; + private float tpf; + private float fps; + + public AndroidTimer() { + //startTime = System.currentTimeMillis(); + startTime = System.nanoTime(); + } + + /** + * Returns the time in seconds. The timer starts + * at 0.0 seconds. + * + * @return the current time in seconds + */ + @Override + public float getTimeInSeconds() { + return getTime() * INVERSE_TIMER_RESOLUTION; + } + + public long getTime() { + //return System.currentTimeMillis() - startTime; + return System.nanoTime() - startTime; + } + + public long getResolution() { + return TIMER_RESOLUTION; + } + + public float getFrameRate() { + return fps; + } + + public float getTimePerFrame() { + return tpf; + } + + public void update() { + tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION); + fps = 1.0f / tpf; + previousTime = getTime(); + } + + public void reset() { + //startTime = System.currentTimeMillis(); + startTime = System.nanoTime(); + previousTime = getTime(); + } +} diff --git a/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java b/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java new file mode 100644 index 0000000..6550101 --- /dev/null +++ b/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java @@ -0,0 +1,134 @@ +package com.jme3.system.android;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.os.Environment;
+import com.jme3.asset.AndroidAssetManager;
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.android.AndroidAudioRenderer;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.system.JmeSystemDelegate;
+import com.jme3.system.Platform;
+import com.jme3.util.AndroidLogHandler;
+import com.jme3.util.JmeFormatter;
+import java.io.File;
+import java.net.URL;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+
+public class JmeAndroidSystem extends JmeSystemDelegate {
+
+ private static Resources res;
+ private static Activity activity;
+
+ @Override
+ public AssetManager newAssetManager(URL configFile) {
+ logger.log(Level.INFO, "newAssetManager({0})", configFile);
+ return new AndroidAssetManager(configFile);
+ }
+
+ @Override
+ public AssetManager newAssetManager() {
+ logger.log(Level.INFO, "newAssetManager()");
+ return new AndroidAssetManager(null);
+ }
+
+ @Override
+ public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
+ return true;
+ }
+
+ @Override
+ public JmeContext newContext(AppSettings settings, Type contextType) {
+ initialize(settings);
+ return new OGLESContext();
+ }
+
+ @Override
+ public AudioRenderer newAudioRenderer(AppSettings settings) {
+ return new AndroidAudioRenderer(activity);
+ }
+
+ @Override
+ public void initialize(AppSettings settings) {
+ if (initialized) {
+ return;
+ }
+
+ initialized = true;
+ try {
+ JmeFormatter formatter = new JmeFormatter();
+
+ Handler consoleHandler = new AndroidLogHandler();
+ consoleHandler.setFormatter(formatter);
+ } catch (SecurityException ex) {
+ logger.log(Level.SEVERE, "Security error in creating log file", ex);
+ }
+ logger.log(Level.INFO, "Running on {0}", getFullName());
+ }
+
+ @Override
+ public Platform getPlatform() {
+ String arch = System.getProperty("os.arch").toLowerCase();
+ if (arch.contains("arm")) {
+ if (arch.contains("v5")) {
+ return Platform.Android_ARM5;
+ } else if (arch.contains("v6")) {
+ return Platform.Android_ARM6;
+ } else if (arch.contains("v7")) {
+ return Platform.Android_ARM7;
+ } else {
+ return Platform.Android_ARM5; // unknown ARM
+ }
+ } else {
+ throw new UnsupportedOperationException("Unsupported Android Platform");
+ }
+ }
+
+ @Override
+ public synchronized File getStorageFolder() {
+ //http://developer.android.com/reference/android/content/Context.html#getExternalFilesDir
+ //http://developer.android.com/guide/topics/data/data-storage.html
+
+ boolean mExternalStorageWriteable = false;
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ mExternalStorageWriteable = true;
+ } else {
+ mExternalStorageWriteable = false;
+ }
+
+ if (mExternalStorageWriteable) {
+ //getExternalFilesDir automatically creates the directory if necessary.
+ //directory structure should be: /mnt/sdcard/Android/data/<packagename>/files
+ //when created this way, the directory is automatically removed by the Android
+ // system when the app is uninstalled
+ storageFolder = activity.getApplicationContext().getExternalFilesDir(null);
+ logger.log(Level.INFO, "Storage Folder Path: {0}", storageFolder.getAbsolutePath());
+
+ return storageFolder;
+ } else {
+ return null;
+ }
+
+ }
+
+ public static void setResources(Resources res) {
+ JmeAndroidSystem.res = res;
+ }
+
+ public static Resources getResources() {
+ return res;
+ }
+
+ public static void setActivity(Activity activity) {
+ JmeAndroidSystem.activity = activity;
+ }
+
+ public static Activity getActivity() {
+ return activity;
+ }
+}
diff --git a/engine/src/android/com/jme3/system/android/OGLESContext.java b/engine/src/android/com/jme3/system/android/OGLESContext.java new file mode 100644 index 0000000..668b68d --- /dev/null +++ b/engine/src/android/com/jme3/system/android/OGLESContext.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2003-2009 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.Activity; +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.view.SurfaceHolder; +import com.jme3.app.AndroidHarness; +import com.jme3.app.Application; +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.android.AndroidInput; +import com.jme3.input.controls.TouchTrigger; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jme3.renderer.android.OGLESShaderRenderer; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; +import com.jme3.system.SystemListener; +import com.jme3.system.Timer; +import com.jme3.system.android.AndroidConfigChooser.ConfigType; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.opengles.GL10; + +public class OGLESContext implements JmeContext, GLSurfaceView.Renderer { + + 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 final AppSettings settings = new AppSettings(true); + + /* + * >= OpenGL ES 2.0 (Android 2.2+) + */ + protected OGLESShaderRenderer renderer; + protected Timer timer; + protected SystemListener listener; + protected boolean autoFlush = true; + protected AndroidInput view; + private boolean firstDrawFrame = true; + + //protected int minFrameDuration = 1000 / frameRate; // Set a max FPS of 33 + protected int minFrameDuration = 0; // No FPS cap + /** + * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 | + * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0 + */ + protected int clientOpenGLESVersion = 1; + protected boolean verboseLogging = false; + final private String ESCAPE_EVENT = "TouchEscape"; + + public OGLESContext() { + } + + @Override + public Type getType() { + return Type.Display; + } + + /** + * <code>createView</code> + * + * @param activity The Android activity which is parent for the + * GLSurfaceView + * @return GLSurfaceView The newly created view + */ + public GLSurfaceView createView(Activity activity) { + return createView(new AndroidInput(activity)); + } + + /** + * <code>createView</code> + * + * @param view The Android input which will be used as the GLSurfaceView for + * this context + * @return GLSurfaceView The newly created view + */ + public GLSurfaceView createView(AndroidInput view) { + return createView(view, ConfigType.FASTEST, false); + } + + /** + * <code>createView</code> initializes the GLSurfaceView + * + * @param view The Android input which will be used as the GLSurfaceView for + * this context + * @param configType ConfigType.FASTEST (Default) | ConfigType.LEGACY | + * ConfigType.BEST + * @param eglConfigVerboseLogging if true show all found configs + * @return GLSurfaceView The newly created view + */ + public GLSurfaceView createView(AndroidInput view, ConfigType configType, boolean eglConfigVerboseLogging) { + // Start to set up the view + this.view = view; + verboseLogging = eglConfigVerboseLogging; + + if (configType == ConfigType.LEGACY) { + // Hardcoded egl setup + clientOpenGLESVersion = 2; + view.setEGLContextClientVersion(2); + //RGB565, Depth16 + view.setEGLConfigChooser(5, 6, 5, 0, 16, 0); + logger.info("ConfigType.LEGACY using RGB565"); + } else { + EGL10 egl = (EGL10) EGLContext.getEGL(); + EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + int[] version = new int[2]; + if (egl.eglInitialize(display, version) == true) { + logger.info("Display EGL Version: " + version[0] + "." + version[1]); + } + + try { + // Create a config chooser + AndroidConfigChooser configChooser = new AndroidConfigChooser(configType, eglConfigVerboseLogging); + // Init chooser + if (!configChooser.findConfig(egl, display)) { + listener.handleError("Unable to find suitable EGL config", null); + return null; + } + + clientOpenGLESVersion = configChooser.getClientOpenGLESVersion(); + if (clientOpenGLESVersion < 2) { + listener.handleError("OpenGL ES 2.0 is not supported on this device", null); + return null; + } + + // Requesting client version from GLSurfaceView which is extended by + // AndroidInput. + view.setEGLContextClientVersion(clientOpenGLESVersion); + view.setEGLConfigChooser(configChooser); + view.getHolder().setFormat(configChooser.getPixelFormat()); + } finally { + if (display != null) { + egl.eglTerminate(display); + } + } + } + + view.setFocusableInTouchMode(true); + view.setFocusable(true); + view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); + view.setRenderer(this); + + return view; + } + + // renderer:initialize + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig cfg) { + + if (created.get() && renderer != null) { + renderer.resetGLObjects(); + } else { + if (!created.get()) { + logger.info("GL Surface created, doing JME3 init"); + initInThread(); + } else { + logger.warning("GL Surface already created"); + } + } + } + + protected void initInThread() { + created.set(true); + + logger.info("OGLESContext create"); + logger.info("Running on thread: " + Thread.currentThread().getName()); + + final Context ctx = this.view.getContext(); + + // Setup unhandled Exception Handler + if (ctx instanceof AndroidHarness) { + Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + ((AndroidHarness) ctx).handleError("Exception thrown in " + thread.toString(), thrown); + } + }); + } else { + Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Exception thrown in " + thread.toString(), thrown); + } + }); + } + + if (clientOpenGLESVersion < 2) { + throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device"); + } + + timer = new AndroidTimer(); + renderer = new OGLESShaderRenderer(); + + renderer.setUseVA(true); + renderer.setVerboseLogging(verboseLogging); + + renderer.initialize(); + listener.initialize(); + + // Setup exit hook + if (ctx instanceof AndroidHarness) { + Application app = ((AndroidHarness) ctx).getJmeApplication(); + if (app.getInputManager() != null) { + app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); + app.getInputManager().addListener((AndroidHarness) ctx, new String[]{ESCAPE_EVENT}); + } + } + + needClose.set(false); + renderable.set(true); + } + + /** + * De-initialize in the OpenGL thread. + */ + protected void deinitInThread() { + if (renderable.get()) { + created.set(false); + if (renderer != null) { + renderer.cleanup(); + } + + listener.destroy(); + + listener = null; + renderer = null; + timer = null; + + // do android specific cleaning here + logger.info("Display destroyed."); + + renderable.set(false); + } + } + + protected void applySettingsToRenderer(OGLESShaderRenderer renderer, AppSettings settings) { + logger.warning("setSettings.USE_VA: [" + settings.getBoolean("USE_VA") + "]"); + logger.warning("setSettings.VERBOSE_LOGGING: [" + settings.getBoolean("VERBOSE_LOGGING") + "]"); + renderer.setUseVA(settings.getBoolean("USE_VA")); + renderer.setVerboseLogging(settings.getBoolean("VERBOSE_LOGGING")); + } + + protected void applySettings(AppSettings settings) { + setSettings(settings); + if (renderer != null) { + applySettingsToRenderer(renderer, this.settings); + } + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + } + + @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 null; + } + + @Override + public TouchInput getTouchInput() { + return view; + } + + @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) { + logger.info("GL Surface changed, width: " + width + " height: " + height); + settings.setResolution(width, height); + listener.reshape(width, height); + } + + // SystemListener:update + @Override + public void onDrawFrame(GL10 gl) { + if (needClose.get()) { + deinitInThread(); + return; + } + + if (renderable.get()) { + if (!created.get()) { + throw new IllegalStateException("onDrawFrame without create"); + } + + long milliStart = System.currentTimeMillis(); + + listener.update(); + + // call to AndroidHarness to remove the splash screen, if present. + // call after listener.update() to make sure no gap between + // splash screen going away and app display being shown. + if (firstDrawFrame) { + final Context ctx = this.view.getContext(); + if (ctx instanceof AndroidHarness) { + ((AndroidHarness) ctx).removeSplashScreen(); + } + firstDrawFrame = false; + } + + if (autoFlush) { + renderer.onFrame(); + } + + long milliDelta = System.currentTimeMillis() - milliStart; + + // Enforce a FPS cap + if (milliDelta < minFrameDuration) { + //logger.log(Level.INFO, "Time per frame {0}", milliDelta); + try { + Thread.sleep(minFrameDuration - milliDelta); + } catch (InterruptedException e) { + } + } + + } + } + + @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) { + } + } + } + + public int getClientOpenGLESVersion() { + return clientOpenGLESVersion; + } +} diff --git a/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java b/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java new file mode 100644 index 0000000..17f850e --- /dev/null +++ b/engine/src/android/com/jme3/texture/plugins/AndroidImageLoader.java @@ -0,0 +1,20 @@ +package com.jme3.texture.plugins; + +import android.graphics.Bitmap; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.texture.Image; +import java.io.IOException; + +public class AndroidImageLoader implements AssetLoader { + + public Object load(AssetInfo info) throws IOException { + AndroidImageInfo imageInfo = new AndroidImageInfo(info); + Bitmap bitmap = imageInfo.getBitmap(); + + Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null); + image.setEfficentData(imageInfo); + return image; + } +} diff --git a/engine/src/android/com/jme3/util/AndroidLogHandler.java b/engine/src/android/com/jme3/util/AndroidLogHandler.java new file mode 100644 index 0000000..8fb21c2 --- /dev/null +++ b/engine/src/android/com/jme3/util/AndroidLogHandler.java @@ -0,0 +1,37 @@ +package com.jme3.util; + +import android.util.Log; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class AndroidLogHandler extends Handler { + + @Override + public void close() { + } + + @Override + public void flush() { + } + + @Override + public void publish(LogRecord record) { + Level level = record.getLevel(); + String clsName = record.getSourceClassName(); + String msg = record.getMessage(); + Throwable t = record.getThrown(); + if (level == Level.INFO){ + Log.i(clsName, msg, t); + }else if (level == Level.SEVERE){ + Log.e(clsName, msg, t); + }else if (level == Level.WARNING){ + Log.w(clsName, msg, t); + }else if (level == Level.CONFIG){ + Log.d(clsName, msg, t); + }else if (level == Level.FINE || level == Level.FINER || level == Level.FINEST){ + Log.v(clsName, msg, t); + } + } + +} diff --git a/engine/src/android/com/jme3/util/FastInteger.java b/engine/src/android/com/jme3/util/FastInteger.java new file mode 100644 index 0000000..49294b4 --- /dev/null +++ b/engine/src/android/com/jme3/util/FastInteger.java @@ -0,0 +1,359 @@ +package com.jme3.util; + + +/** + * The wrapper for the primitive type {@code int}. + * <p> + * As with the specification, this implementation relies on code laid out in <a + * href="http://www.hackersdelight.org/">Henry S. Warren, Jr.'s Hacker's + * Delight, (Addison Wesley, 2002)</a> as well as <a + * href="http://aggregate.org/MAGIC/">The Aggregate's Magic Algorithms</a>. + * + * @see java.lang.Number + * @since 1.1 + */ +public final class FastInteger { + + /** + * Constant for the maximum {@code int} value, 2<sup>31</sup>-1. + */ + public static final int MAX_VALUE = 0x7FFFFFFF; + + /** + * Constant for the minimum {@code int} value, -2<sup>31</sup>. + */ + public static final int MIN_VALUE = 0x80000000; + + /** + * Constant for the number of bits needed to represent an {@code int} in + * two's complement form. + * + * @since 1.5 + */ + public static final int SIZE = 32; + + /* + * Progressively smaller decimal order of magnitude that can be represented + * by an instance of Integer. Used to help compute the String + * representation. + */ + private static final int[] decimalScale = new int[] { 1000000000, 100000000, + 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 }; + + /** + * Converts the specified integer into its decimal string representation. + * The returned string is a concatenation of a minus sign if the number is + * negative and characters from '0' to '9'. + * + * @param value + * the integer to convert. + * @return the decimal string representation of {@code value}. + */ + public static boolean toCharArray(int value, char[] output) { + if (value == 0) + { + output[0] = '0'; + output[1] = 0; + return true; + } + + // Faster algorithm for smaller Integers + if (value < 1000 && value > -1000) { + + int positive_value = value < 0 ? -value : value; + int first_digit = 0; + if (value < 0) { + output[0] = '-'; + first_digit++; + } + int last_digit = first_digit; + int quot = positive_value; + do { + int res = quot / 10; + int digit_value = quot - ((res << 3) + (res << 1)); + digit_value += '0'; + output[last_digit++] = (char) digit_value; + quot = res; + } while (quot != 0); + + int count = last_digit--; + do { + char tmp = output[last_digit]; + output[last_digit--] = output[first_digit]; + output[first_digit++] = tmp; + } while (first_digit < last_digit); + output[count] = 0; + return true; + } + if (value == MIN_VALUE) { + System.arraycopy("-2147483648".toCharArray(), 0, output, 0, 12); + output[12] = 0; + return true; + } + + + int positive_value = value < 0 ? -value : value; + byte first_digit = 0; + if (value < 0) { + output[0] = '-'; + first_digit++; + } + byte last_digit = first_digit; + byte count; + int number; + boolean start = false; + for (int i = 0; i < 9; i++) { + count = 0; + if (positive_value < (number = decimalScale[i])) { + if (start) { + output[last_digit++] = '0'; + } + continue; + } + + if (i > 0) { + number = (decimalScale[i] << 3); + if (positive_value >= number) { + positive_value -= number; + count += 8; + } + number = (decimalScale[i] << 2); + if (positive_value >= number) { + positive_value -= number; + count += 4; + } + } + number = (decimalScale[i] << 1); + if (positive_value >= number) { + positive_value -= number; + count += 2; + } + if (positive_value >= decimalScale[i]) { + positive_value -= decimalScale[i]; + count++; + } + if (count > 0 && !start) { + start = true; + } + if (start) { + output[last_digit++] = (char) (count + '0'); + } + } + + output[last_digit++] = (char) (positive_value + '0'); + output[last_digit] = 0; + count = last_digit--; + return true; + } + + + /** + * Determines the highest (leftmost) bit of the specified integer that is 1 + * and returns the bit mask value for that bit. This is also referred to as + * the Most Significant 1 Bit. Returns zero if the specified integer is + * zero. + * + * @param i + * the integer to examine. + * @return the bit mask indicating the highest 1 bit in {@code i}. + * @since 1.5 + */ + public static int highestOneBit(int i) { + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return (i & ~(i >>> 1)); + } + + /** + * Determines the lowest (rightmost) bit of the specified integer that is 1 + * and returns the bit mask value for that bit. This is also referred + * to as the Least Significant 1 Bit. Returns zero if the specified integer + * is zero. + * + * @param i + * the integer to examine. + * @return the bit mask indicating the lowest 1 bit in {@code i}. + * @since 1.5 + */ + public static int lowestOneBit(int i) { + return (i & (-i)); + } + + /** + * Determines the number of leading zeros in the specified integer prior to + * the {@link #highestOneBit(int) highest one bit}. + * + * @param i + * the integer to examine. + * @return the number of leading zeros in {@code i}. + * @since 1.5 + */ + public static int numberOfLeadingZeros(int i) { + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return bitCount(~i); + } + + /** + * Determines the number of trailing zeros in the specified integer after + * the {@link #lowestOneBit(int) lowest one bit}. + * + * @param i + * the integer to examine. + * @return the number of trailing zeros in {@code i}. + * @since 1.5 + */ + public static int numberOfTrailingZeros(int i) { + return bitCount((i & -i) - 1); + } + + /** + * Counts the number of 1 bits in the specified integer; this is also + * referred to as population count. + * + * @param i + * the integer to examine. + * @return the number of 1 bits in {@code i}. + * @since 1.5 + */ + public static int bitCount(int i) { + i -= ((i >> 1) & 0x55555555); + i = (i & 0x33333333) + ((i >> 2) & 0x33333333); + i = (((i >> 4) + i) & 0x0F0F0F0F); + i += (i >> 8); + i += (i >> 16); + return (i & 0x0000003F); + } + + /** + * Rotates the bits of the specified integer to the left by the specified + * number of bits. + * + * @param i + * the integer value to rotate left. + * @param distance + * the number of bits to rotate. + * @return the rotated value. + * @since 1.5 + */ + public static int rotateLeft(int i, int distance) { + if (distance == 0) { + return i; + } + /* + * According to JLS3, 15.19, the right operand of a shift is always + * implicitly masked with 0x1F, which the negation of 'distance' is + * taking advantage of. + */ + return ((i << distance) | (i >>> (-distance))); + } + + /** + * Rotates the bits of the specified integer to the right by the specified + * number of bits. + * + * @param i + * the integer value to rotate right. + * @param distance + * the number of bits to rotate. + * @return the rotated value. + * @since 1.5 + */ + public static int rotateRight(int i, int distance) { + if (distance == 0) { + return i; + } + /* + * According to JLS3, 15.19, the right operand of a shift is always + * implicitly masked with 0x1F, which the negation of 'distance' is + * taking advantage of. + */ + return ((i >>> distance) | (i << (-distance))); + } + + /** + * Reverses the order of the bytes of the specified integer. + * + * @param i + * the integer value for which to reverse the byte order. + * @return the reversed value. + * @since 1.5 + */ + public static int reverseBytes(int i) { + int b3 = i >>> 24; + int b2 = (i >>> 8) & 0xFF00; + int b1 = (i & 0xFF00) << 8; + int b0 = i << 24; + return (b0 | b1 | b2 | b3); + } + + /** + * Reverses the order of the bits of the specified integer. + * + * @param i + * the integer value for which to reverse the bit order. + * @return the reversed value. + * @since 1.5 + */ + public static int reverse(int i) { + // From Hacker's Delight, 7-1, Figure 7-1 + i = (i & 0x55555555) << 1 | (i >> 1) & 0x55555555; + i = (i & 0x33333333) << 2 | (i >> 2) & 0x33333333; + i = (i & 0x0F0F0F0F) << 4 | (i >> 4) & 0x0F0F0F0F; + return reverseBytes(i); + } + + /** + * Returns the value of the {@code signum} function for the specified + * integer. + * + * @param i + * the integer value to check. + * @return -1 if {@code i} is negative, 1 if {@code i} is positive, 0 if + * {@code i} is zero. + * @since 1.5 + */ + public static int signum(int i) { + return (i == 0 ? 0 : (i < 0 ? -1 : 1)); + } + + /** + * Returns a {@code Integer} instance for the specified integer value. + * <p> + * If it is not necessary to get a new {@code Integer} instance, it is + * recommended to use this method instead of the constructor, since it + * maintains a cache of instances which may result in better performance. + * + * @param i + * the integer value to store in the instance. + * @return a {@code Integer} instance containing {@code i}. + * @since 1.5 + */ + public static Integer valueOf(int i) { + if (i < -128 || i > 127) { + return new Integer(i); + } + return valueOfCache.CACHE [i+128]; + + } + + static class valueOfCache { + /** + * <p> + * A cache of instances used by {@link Integer#valueOf(int)} and auto-boxing. + */ + static final Integer[] CACHE = new Integer[256]; + + static { + for(int i=-128; i<=127; i++) { + CACHE[i+128] = new Integer(i); + } + } + } +} diff --git a/engine/src/android/com/jme3/util/RingBuffer.java b/engine/src/android/com/jme3/util/RingBuffer.java new file mode 100644 index 0000000..786417b --- /dev/null +++ b/engine/src/android/com/jme3/util/RingBuffer.java @@ -0,0 +1,75 @@ +package com.jme3.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Ring buffer (fixed size queue) implementation using a circular array (array with wrap-around). + */ +// suppress unchecked warnings in Java 1.5.0_6 and later +@SuppressWarnings("unchecked") +public class RingBuffer<Item> implements Iterable<Item> { + + private Item[] buffer; // queue elements + private int count = 0; // number of elements on queue + private int indexOut = 0; // index of first element of queue + private int indexIn = 0; // index of next available slot + + // cast needed since no generic array creation in Java + public RingBuffer(int capacity) { + buffer = (Item[]) new Object[capacity]; + } + + public boolean isEmpty() { + return count == 0; + } + + public int size() { + return count; + } + + public void push(Item item) { + if (count == buffer.length) { + throw new RuntimeException("Ring buffer overflow"); + } + buffer[indexIn] = item; + indexIn = (indexIn + 1) % buffer.length; // wrap-around + count++; + } + + public Item pop() { + if (isEmpty()) { + throw new RuntimeException("Ring buffer underflow"); + } + Item item = buffer[indexOut]; + buffer[indexOut] = null; // to help with garbage collection + count--; + indexOut = (indexOut + 1) % buffer.length; // wrap-around + return item; + } + + public Iterator<Item> iterator() { + return new RingBufferIterator(); + } + + // an iterator, doesn't implement remove() since it's optional + private class RingBufferIterator implements Iterator<Item> { + + private int i = 0; + + public boolean hasNext() { + return i < count; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public Item next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return buffer[i++]; + } + } +} |