diff --git a/modules/gdnative/android/android_gdn.cpp b/modules/gdnative/android/android_gdn.cpp index 8657935602e..624ef19dec5 100644 --- a/modules/gdnative/android/android_gdn.cpp +++ b/modules/gdnative/android/android_gdn.cpp @@ -34,6 +34,8 @@ // These entry points are only for the android platform and are simple stubs in all others. #ifdef __ANDROID__ +#include "platform/android/java_godot_wrapper.h" +#include "platform/android/os_android.h" #include "platform/android/thread_jandroid.h" #else #define JNIEnv void @@ -54,20 +56,31 @@ JNIEnv *GDAPI godot_android_get_env() { jobject GDAPI godot_android_get_activity() { #ifdef __ANDROID__ - JNIEnv *env = ThreadAndroid::get_env(); - - jclass activityThread = env->FindClass("android/app/ActivityThread"); - jmethodID currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); - jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread); - jmethodID getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;"); - jobject context = env->CallObjectMethod(at, getApplication); - - return env->NewGlobalRef(context); + OS_Android *os_android = (OS_Android *)OS::get_singleton(); + return os_android->get_godot_java()->get_activity(); #else return NULL; #endif } +jobject GDAPI godot_android_get_surface() { +#ifdef __ANDROID__ + OS_Android *os_android = (OS_Android *)OS::get_singleton(); + return os_android->get_godot_java()->get_surface(); +#else + return NULL; +#endif +} + +bool GDAPI godot_android_is_activity_resumed() { +#ifdef __ANDROID__ + OS_Android *os_android = (OS_Android *)OS::get_singleton(); + return os_android->get_godot_java()->is_activity_resumed(); +#else + return false; +#endif +} + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 8afe9881029..9f3c5752fde 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -6286,7 +6286,7 @@ "type": "ANDROID", "version": { "major": 1, - "minor": 0 + "minor": 1 }, "next": null, "api": [ @@ -6301,6 +6301,18 @@ "return_type": "jobject", "arguments": [ ] + }, + { + "name": "godot_android_get_surface", + "return_type": "jobject", + "arguments": [ + ] + }, + { + "name": "godot_android_is_activity_resumed", + "return_type": "bool", + "arguments": [ + ] } ] }, diff --git a/modules/gdnative/include/android/godot_android.h b/modules/gdnative/include/android/godot_android.h index 32e86838be1..7063e1d2c59 100644 --- a/modules/gdnative/include/android/godot_android.h +++ b/modules/gdnative/include/android/godot_android.h @@ -46,6 +46,8 @@ extern "C" { JNIEnv *GDAPI godot_android_get_env(); jobject GDAPI godot_android_get_activity(); +jobject GDAPI godot_android_get_surface(); +bool GDAPI godot_android_is_activity_resumed(); #ifdef __cplusplus } diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java index 0eeaf0701cd..751e8851186 100644 --- a/platform/android/java/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/src/org/godotengine/godot/Godot.java @@ -30,8 +30,6 @@ package org.godotengine.godot; -//import android.R; - import android.Manifest; import android.app.Activity; import android.app.ActivityManager; @@ -64,6 +62,7 @@ import android.util.Log; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; @@ -73,7 +72,6 @@ import android.view.WindowManager; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ProgressBar; -import android.widget.RelativeLayout; import android.widget.TextView; import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; @@ -94,6 +92,7 @@ import java.util.Locale; import javax.microedition.khronos.opengles.GL10; import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.payments.PaymentsManager; +import org.godotengine.godot.xr.XRMode; public class Godot extends Activity implements SensorEventListener, IDownloaderClient { @@ -120,6 +119,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC private boolean use_immersive = false; private boolean use_debug_opengl = false; private boolean mStatePaused; + private boolean activityResumed; private int mState; static private Intent mCurrentIntent; @@ -282,7 +282,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC // ...add to FrameLayout layout.addView(edittext); - mView = new GodotView(getApplication(), io, use_gl3, use_32_bits, use_debug_opengl, this); + mView = new GodotView(this, XRMode.PANCAKE, use_gl3, use_32_bits, use_debug_opengl); layout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); edittext.setView(mView); io.setEdit(edittext); @@ -402,6 +402,20 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } } + /** + * Used by the native code (java_godot_wrapper.h) to check whether the activity is resumed or paused. + */ + private boolean isActivityResumed() { + return activityResumed; + } + + /** + * Used by the native code (java_godot_wrapper.h) to access the Android surface. + */ + private Surface getSurface() { + return mView.getHolder().getSurface(); + } + String expansion_pack_path; private void initializeGodot() { @@ -612,6 +626,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC @Override protected void onPause() { super.onPause(); + activityResumed = false; + if (!godot_initialized) { if (null != mDownloaderClientStub) { mDownloaderClientStub.disconnect(this); @@ -687,6 +703,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC singletons[i].onMainResume(); } + + activityResumed = true; } public void UiChangeListener() { diff --git a/platform/android/java/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/src/org/godotengine/godot/GodotRenderer.java new file mode 100644 index 00000000000..8e3775c2a94 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/GodotRenderer.java @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* GodotRenderer.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.utils.GLUtils; + +/** + * Godot's renderer implementation. + */ +class GodotRenderer implements GLSurfaceView.Renderer { + + public void onDrawFrame(GL10 gl) { + GodotLib.step(); + for (int i = 0; i < Godot.singleton_count; i++) { + Godot.singletons[i].onGLDrawFrame(gl); + } + } + + public void onSurfaceChanged(GL10 gl, int width, int height) { + + GodotLib.resize(width, height); + for (int i = 0; i < Godot.singleton_count; i++) { + Godot.singletons[i].onGLSurfaceChanged(gl, width, height); + } + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GodotLib.newcontext(GLUtils.use_32); + } +} diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java index ab28d9ec333..1c189a15797 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotView.java +++ b/platform/android/java/src/org/godotengine/godot/GodotView.java @@ -30,28 +30,20 @@ package org.godotengine.godot; import android.annotation.SuppressLint; -import android.content.Context; -import android.content.ContextWrapper; import android.graphics.PixelFormat; -import android.hardware.input.InputManager; import android.opengl.GLSurfaceView; -import android.util.AttributeSet; -import android.util.Log; -import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -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; -import org.godotengine.godot.input.InputManagerCompat; -import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; +import org.godotengine.godot.input.GodotInputHandler; +import org.godotengine.godot.utils.GLUtils; +import org.godotengine.godot.xr.XRMode; +import org.godotengine.godot.xr.ovr.OvrConfigChooser; +import org.godotengine.godot.xr.ovr.OvrContextFactory; +import org.godotengine.godot.xr.ovr.OvrWindowSurfaceFactory; +import org.godotengine.godot.xr.pancake.PancakeConfigChooser; +import org.godotengine.godot.xr.pancake.PancakeContextFactory; +import org.godotengine.godot.xr.pancake.PancakeFallbackConfigChooser; + /** * A simple GLSurfaceView sub-class that demonstrate how to perform * OpenGL ES 2.0 rendering into a GL Surface. Note the following important @@ -70,57 +62,26 @@ import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; * that matches it exactly (with regards to red/green/blue/alpha channels * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. */ -public class GodotView extends GLSurfaceView implements InputDeviceListener { +public class GodotView extends GLSurfaceView { - private static String TAG = "GodotView"; - private static final boolean DEBUG = false; - private Context ctx; + private static String TAG = GodotView.class.getSimpleName(); - private GodotIO io; - private static boolean use_gl3 = false; - private static boolean use_32 = false; - private static boolean use_debug_opengl = false; + private final Godot activity; + private final GodotInputHandler inputHandler; - private Godot activity; + public GodotView(Godot activity, XRMode xrMode, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl) { + super(activity); + GLUtils.use_gl3 = p_use_gl3; + GLUtils.use_32 = p_use_32_bits; + GLUtils.use_debug_opengl = p_use_debug_opengl; - private InputManagerCompat mInputManager; - public GodotView(Context context, GodotIO p_io, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl, Godot p_activity) { - super(context); - ctx = context; - io = p_io; - use_gl3 = p_use_gl3; - use_32 = p_use_32_bits; - use_debug_opengl = p_use_debug_opengl; - - activity = p_activity; - - setPreserveEGLContextOnPause(true); - - mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext()); - mInputManager.registerInputDeviceListener(this, null); - init(false, 16, 0); - } - - public GodotView(Context context) { - super(context); - ctx = context; - } - - public GodotView(Context context, boolean translucent, int depth, int stencil) { - super(context); - init(translucent, depth, stencil); + this.activity = activity; + this.inputHandler = new GodotInputHandler(this); + init(xrMode, false, 16, 0); } public void initInputDevices() { - /* initially add input devices*/ - int[] deviceIds = mInputManager.getInputDeviceIds(); - for (int deviceId : deviceIds) { - InputDevice device = mInputManager.getInputDevice(deviceId); - if (DEBUG) { - Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); - } - onInputDeviceAdded(deviceId); - } + this.inputHandler.initInputDevices(); } @SuppressLint("ClickableViewAccessibility") @@ -130,617 +91,80 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { return activity.gotTouchEvent(event); } - public int get_godot_button(int keyCode) { - - int button; - switch (keyCode) { - case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B - button = 0; - break; - case KeyEvent.KEYCODE_BUTTON_B: - button = 1; - break; - case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y - button = 2; - break; - case KeyEvent.KEYCODE_BUTTON_Y: - button = 3; - break; - case KeyEvent.KEYCODE_BUTTON_L1: - button = 9; - break; - case KeyEvent.KEYCODE_BUTTON_L2: - button = 15; - break; - case KeyEvent.KEYCODE_BUTTON_R1: - button = 10; - break; - case KeyEvent.KEYCODE_BUTTON_R2: - button = 16; - break; - case KeyEvent.KEYCODE_BUTTON_SELECT: - button = 4; - break; - case KeyEvent.KEYCODE_BUTTON_START: - button = 6; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: - button = 7; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBR: - button = 8; - break; - case KeyEvent.KEYCODE_DPAD_UP: - button = 11; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - button = 12; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - button = 13; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - button = 14; - break; - case KeyEvent.KEYCODE_BUTTON_C: - button = 17; - break; - case KeyEvent.KEYCODE_BUTTON_Z: - button = 18; - break; - - default: - button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20; - break; - } - return button; - }; - - private static class joystick { - public int device_id; - public String name; - public ArrayList axes; - public ArrayList hats; - } - - private static class RangeComparator implements Comparator { - @Override - public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { - return arg0.getAxis() - arg1.getAxis(); - } - } - - ArrayList joy_devices = new ArrayList(); - - private int find_joy_device(int device_id) { - for (int i = 0; i < joy_devices.size(); i++) { - if (joy_devices.get(i).device_id == device_id) { - return i; - } - } - - return -1; - } - - @Override - public void onInputDeviceAdded(int deviceId) { - int id = find_joy_device(deviceId); - - // Check if the device has not been already added - if (id < 0) { - InputDevice device = mInputManager.getInputDevice(deviceId); - //device can be null if deviceId is not found - if (device != null) { - int sources = device.getSources(); - if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || - ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { - id = joy_devices.size(); - - joystick joy = new joystick(); - joy.device_id = deviceId; - joy.name = device.getName(); - joy.axes = new ArrayList(); - joy.hats = new ArrayList(); - - List ranges = device.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); - - for (InputDevice.MotionRange range : ranges) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joy.hats.add(range); - } else { - joy.axes.add(range); - } - } - - joy_devices.add(joy); - - final int device_id = id; - final String name = joy.name; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, true, name); - } - }); - } - } - } - } - - @Override - public void onInputDeviceRemoved(int deviceId) { - final int device_id = find_joy_device(deviceId); - - // Check if the evice has not been already removed - if (device_id > -1) { - joy_devices.remove(device_id); - - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, false, ""); - } - }); - } - } - - @Override - public void onInputDeviceChanged(int deviceId) { - onInputDeviceRemoved(deviceId); - onInputDeviceAdded(deviceId); - } @Override public boolean onKeyUp(final int keyCode, KeyEvent event) { - - if (keyCode == KeyEvent.KEYCODE_BACK) { - return true; - } - - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - return super.onKeyUp(keyCode, event); - }; - - int source = event.getSource(); - if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - - final int button = get_godot_button(keyCode); - final int device_id = find_joy_device(event.getDeviceId()); - - // Check if the device exists - if (device_id > -1) { - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joybutton(device_id, button, false); - } - }); - return true; - } - } else { - final int chr = event.getUnicodeChar(0); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.key(keyCode, chr, false); - } - }); - }; - - return super.onKeyUp(keyCode, event); - }; + return inputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } @Override public boolean onKeyDown(final int keyCode, KeyEvent event) { - - if (keyCode == KeyEvent.KEYCODE_BACK) { - activity.onBackPressed(); - // press 'back' button should not terminate program - //normal handle 'back' event in game logic - return true; - } - - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - return super.onKeyDown(keyCode, event); - }; - - int source = event.getSource(); - //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD))); - - if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - - if (event.getRepeatCount() > 0) // ignore key echo - return true; - - final int button = get_godot_button(keyCode); - final int device_id = find_joy_device(event.getDeviceId()); - - // Check if the device exists - if (device_id > -1) { - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joybutton(device_id, button, true); - } - }); - return true; - } - } else { - final int chr = event.getUnicodeChar(0); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.key(keyCode, chr, true); - } - }); - }; - - return super.onKeyDown(keyCode, event); + return inputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); } @Override public boolean onGenericMotionEvent(MotionEvent event) { + return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); + } - if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { + private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { - final int device_id = find_joy_device(event.getDeviceId()); + setPreserveEGLContextOnPause(true); + setFocusableInTouchMode(true); + switch (xrMode) { - // Check if the device exists - if (device_id > -1) { - joystick joy = joy_devices.get(device_id); + case OVR: + // Replace the default egl config chooser. + setEGLConfigChooser(new OvrConfigChooser()); - for (int i = 0; i < joy.axes.size(); i++) { - InputDevice.MotionRange range = joy.axes.get(i); - final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f; - final int idx = i; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyaxis(device_id, idx, value); - } - }); + // Replace the default context factory. + setEGLContextFactory(new OvrContextFactory()); + + // Replace the default window surface factory. + setEGLWindowSurfaceFactory(new OvrWindowSurfaceFactory()); + break; + + case PANCAKE: + default: + /* By default, GLSurfaceView() creates a RGB_565 opaque surface. + * If we want a translucent one, we should change the surface's + * format here, using PixelFormat.TRANSLUCENT for GL Surfaces + * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. + */ + if (translucent) { + this.getHolder().setFormat(PixelFormat.TRANSLUCENT); } - for (int i = 0; i < joy.hats.size(); i += 2) { - final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); - final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis())); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyhat(device_id, hatX, hatY); - } - }); + /* Setup the context factory for 2.0 rendering. + * See ContextFactory class definition below + */ + setEGLContextFactory(new PancakeContextFactory()); + + /* We need to choose an EGLConfig that matches the format of + * our surface exactly. This is going to be done in our + * custom config chooser. See ConfigChooser class definition + * below. + */ + + if (GLUtils.use_32) { + setEGLConfigChooser(translucent ? + new PancakeFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new PancakeConfigChooser(8, 8, 8, 8, 16, stencil)) : + new PancakeFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new PancakeConfigChooser(5, 6, 5, 0, 16, stencil))); + + } else { + setEGLConfigChooser(translucent ? + new PancakeConfigChooser(8, 8, 8, 8, 16, stencil) : + new PancakeConfigChooser(5, 6, 5, 0, 16, stencil)); } - return true; - } - }; - - return super.onGenericMotionEvent(event); - }; - - private void init(boolean translucent, int depth, int stencil) { - - this.setFocusableInTouchMode(true); - /* By default, GLSurfaceView() creates a RGB_565 opaque surface. - * If we want a translucent one, we should change the surface's - * format here, using PixelFormat.TRANSLUCENT for GL Surfaces - * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. - */ - if (translucent) { - this.getHolder().setFormat(PixelFormat.TRANSLUCENT); - } - - /* Setup the context factory for 2.0 rendering. - * See ContextFactory class definition below - */ - setEGLContextFactory(new ContextFactory()); - - /* We need to choose an EGLConfig that matches the format of - * our surface exactly. This is going to be done in our - * custom config chooser. See ConfigChooser class definition - * below. - */ - - if (use_32) { - setEGLConfigChooser(translucent ? - new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(8, 8, 8, 8, 16, stencil)) : - new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(5, 6, 5, 0, 16, stencil))); - - } else { - setEGLConfigChooser(translucent ? - new ConfigChooser(8, 8, 8, 8, 16, stencil) : - new ConfigChooser(5, 6, 5, 0, 16, stencil)); + break; } /* Set the renderer responsible for frame rendering */ - setRenderer(new Renderer()); + setRenderer(new GodotRenderer()); } - private static final int _EGL_CONTEXT_FLAGS_KHR = 0x30FC; - private static final int _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR = 0x00000001; - - private static class ContextFactory implements GLSurfaceView.EGLContextFactory { - private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { - String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name"); - if (use_gl3 && !driver_name.equals("GLES3")) { - use_gl3 = false; - } - if (use_gl3) - Log.w(TAG, "creating OpenGL ES 3.0 context :"); - else - Log.w(TAG, "creating OpenGL ES 2.0 context :"); - - checkEglError("Before eglCreateContext", egl); - EGLContext context; - if (use_debug_opengl) { - int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; - int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, use_gl3 ? attrib_list3 : attrib_list2); - } else { - int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, use_gl3 ? attrib_list3 : attrib_list2); - } - checkEglError("After eglCreateContext", egl); - return context; - } - - public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { - egl.eglDestroyContext(display, context); - } - } - - private static void checkEglError(String prompt, EGL10 egl) { - int error; - while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { - Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error)); - } - } - /* Fallback if 32bit View is not supported*/ - private static class FallbackConfigChooser extends ConfigChooser { - private ConfigChooser fallback; - - public FallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, ConfigChooser fallback) { - super(r, g, b, a, depth, stencil); - this.fallback = fallback; - } - - @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { - EGLConfig ec = super.chooseConfig(egl, display, configs); - if (ec == null) { - Log.w(TAG, "Trying ConfigChooser fallback"); - ec = fallback.chooseConfig(egl, display, configs); - use_32 = false; - } - return ec; - } - } - - private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser { - - public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) { - mRedSize = r; - mGreenSize = g; - mBlueSize = b; - mAlphaSize = a; - mDepthSize = depth; - mStencilSize = stencil; - } - - /* This EGL config specification is used to specify 2.0 rendering. - * We use a minimum size of 4 bits for red/green/blue, but will - * perform actual matching in chooseConfig() below. - */ - private static int EGL_OPENGL_ES2_BIT = 4; - private static int[] s_configAttribs2 = { - EGL10.EGL_RED_SIZE, 4, - EGL10.EGL_GREEN_SIZE, 4, - EGL10.EGL_BLUE_SIZE, 4, - // EGL10.EGL_DEPTH_SIZE, 16, - // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE - }; - private static int[] s_configAttribs3 = { - EGL10.EGL_RED_SIZE, 4, - EGL10.EGL_GREEN_SIZE, 4, - EGL10.EGL_BLUE_SIZE, 4, - // EGL10.EGL_DEPTH_SIZE, 16, - // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT - EGL10.EGL_NONE - }; - - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - - /* Get the number of minimally matching EGL configurations - */ - int[] num_config = new int[1]; - egl.eglChooseConfig(display, use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config); - - int numConfigs = num_config[0]; - - if (numConfigs <= 0) { - throw new IllegalArgumentException("No configs match configSpec"); - } - - /* Allocate then read the array of minimally matching EGL configs - */ - EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(display, use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config); - - if (DEBUG) { - printConfigs(egl, display, configs); - } - /* Now return the "best" one - */ - return chooseConfig(egl, display, configs); - } - - 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); - - // We need at least mDepthSize and mStencilSize bits - if (d < mDepthSize || s < mStencilSize) - continue; - - // We want an *exact* match for red/green/blue/alpha - 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 void printConfigs(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - int numConfigs = configs.length; - Log.w(TAG, String.format("%d configurations", numConfigs)); - for (int i = 0; i < numConfigs; i++) { - Log.w(TAG, String.format("Configuration %d:\n", i)); - printConfig(egl, display, configs[i]); - } - } - - private void printConfig(EGL10 egl, EGLDisplay display, - EGLConfig config) { - int[] attributes = { - EGL10.EGL_BUFFER_SIZE, - EGL10.EGL_ALPHA_SIZE, - EGL10.EGL_BLUE_SIZE, - EGL10.EGL_GREEN_SIZE, - EGL10.EGL_RED_SIZE, - EGL10.EGL_DEPTH_SIZE, - EGL10.EGL_STENCIL_SIZE, - EGL10.EGL_CONFIG_CAVEAT, - EGL10.EGL_CONFIG_ID, - EGL10.EGL_LEVEL, - EGL10.EGL_MAX_PBUFFER_HEIGHT, - EGL10.EGL_MAX_PBUFFER_PIXELS, - EGL10.EGL_MAX_PBUFFER_WIDTH, - EGL10.EGL_NATIVE_RENDERABLE, - EGL10.EGL_NATIVE_VISUAL_ID, - EGL10.EGL_NATIVE_VISUAL_TYPE, - 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, - EGL10.EGL_SAMPLES, - EGL10.EGL_SAMPLE_BUFFERS, - EGL10.EGL_SURFACE_TYPE, - EGL10.EGL_TRANSPARENT_TYPE, - EGL10.EGL_TRANSPARENT_RED_VALUE, - EGL10.EGL_TRANSPARENT_GREEN_VALUE, - EGL10.EGL_TRANSPARENT_BLUE_VALUE, - 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, - 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, - 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, - 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, - EGL10.EGL_LUMINANCE_SIZE, - EGL10.EGL_ALPHA_MASK_SIZE, - EGL10.EGL_COLOR_BUFFER_TYPE, - EGL10.EGL_RENDERABLE_TYPE, - 0x3042 // EGL10.EGL_CONFORMANT - }; - String[] names = { - "EGL_BUFFER_SIZE", - "EGL_ALPHA_SIZE", - "EGL_BLUE_SIZE", - "EGL_GREEN_SIZE", - "EGL_RED_SIZE", - "EGL_DEPTH_SIZE", - "EGL_STENCIL_SIZE", - "EGL_CONFIG_CAVEAT", - "EGL_CONFIG_ID", - "EGL_LEVEL", - "EGL_MAX_PBUFFER_HEIGHT", - "EGL_MAX_PBUFFER_PIXELS", - "EGL_MAX_PBUFFER_WIDTH", - "EGL_NATIVE_RENDERABLE", - "EGL_NATIVE_VISUAL_ID", - "EGL_NATIVE_VISUAL_TYPE", - "EGL_PRESERVED_RESOURCES", - "EGL_SAMPLES", - "EGL_SAMPLE_BUFFERS", - "EGL_SURFACE_TYPE", - "EGL_TRANSPARENT_TYPE", - "EGL_TRANSPARENT_RED_VALUE", - "EGL_TRANSPARENT_GREEN_VALUE", - "EGL_TRANSPARENT_BLUE_VALUE", - "EGL_BIND_TO_TEXTURE_RGB", - "EGL_BIND_TO_TEXTURE_RGBA", - "EGL_MIN_SWAP_INTERVAL", - "EGL_MAX_SWAP_INTERVAL", - "EGL_LUMINANCE_SIZE", - "EGL_ALPHA_MASK_SIZE", - "EGL_COLOR_BUFFER_TYPE", - "EGL_RENDERABLE_TYPE", - "EGL_CONFORMANT" - }; - int[] value = new int[1]; - for (int i = 0; i < attributes.length; i++) { - int attribute = attributes[i]; - String name = names[i]; - if (egl.eglGetConfigAttrib(display, config, attribute, value)) { - Log.w(TAG, String.format(" %s: %d\n", name, value[0])); - } else { - // Log.w(TAG, String.format(" %s: failed\n", name)); - while (egl.eglGetError() != EGL10.EGL_SUCCESS) - ; - } - } - } - - // Subclasses can adjust these values: - protected int mRedSize; - protected int mGreenSize; - protected int mBlueSize; - protected int mAlphaSize; - protected int mDepthSize; - protected int mStencilSize; - private int[] mValue = new int[1]; - } - - private static class Renderer implements GLSurfaceView.Renderer { - - public void onDrawFrame(GL10 gl) { - GodotLib.step(); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLDrawFrame(gl); - } - } - - public void onSurfaceChanged(GL10 gl, int width, int height) { - - GodotLib.resize(width, height); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLSurfaceChanged(gl, width, height); - } - } - - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - GodotLib.newcontext(use_32); - } + public void onBackPressed() { + activity.onBackPressed(); } } diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java new file mode 100644 index 00000000000..d01f958123f --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java @@ -0,0 +1,352 @@ +/*************************************************************************/ +/* GodotInputHandler.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.input; + +import static org.godotengine.godot.utils.GLUtils.DEBUG; + +import android.util.Log; +import android.view.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyEvent; +import android.view.MotionEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotView; +import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; + +/** + * Handles input related events for the {@link GodotView} view. + */ +public class GodotInputHandler implements InputDeviceListener { + + private final ArrayList joysticksDevices = new ArrayList(); + + private final GodotView godotView; + private final InputManagerCompat inputManager; + + public GodotInputHandler(GodotView godotView) { + this.godotView = godotView; + this.inputManager = InputManagerCompat.Factory.getInputManager(godotView.getContext()); + this.inputManager.registerInputDeviceListener(this, null); + } + + private void queueEvent(Runnable task) { + godotView.queueEvent(task); + } + + public boolean onKeyUp(final int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return true; + } + + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + return false; + }; + + int source = event.getSource(); + if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + + final int button = getGodotButton(keyCode); + final int device_id = findJoystickDevice(event.getDeviceId()); + + // Check if the device exists + if (device_id > -1) { + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joybutton(device_id, button, false); + } + }); + return true; + } + } else { + final int chr = event.getUnicodeChar(0); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.key(keyCode, chr, false); + } + }); + }; + + return false; + } + + public boolean onKeyDown(final int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + godotView.onBackPressed(); + // press 'back' button should not terminate program + //normal handle 'back' event in game logic + return true; + } + + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + return false; + }; + + int source = event.getSource(); + //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD))); + + if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + + if (event.getRepeatCount() > 0) // ignore key echo + return true; + + final int button = getGodotButton(keyCode); + final int device_id = findJoystickDevice(event.getDeviceId()); + + // Check if the device exists + if (device_id > -1) { + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joybutton(device_id, button, true); + } + }); + return true; + } + } else { + final int chr = event.getUnicodeChar(0); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.key(keyCode, chr, true); + } + }); + }; + + return false; + } + + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { + + final int device_id = findJoystickDevice(event.getDeviceId()); + + // Check if the device exists + if (device_id > -1) { + Joystick joy = joysticksDevices.get(device_id); + + for (int i = 0; i < joy.axes.size(); i++) { + InputDevice.MotionRange range = joy.axes.get(i); + final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f; + final int idx = i; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyaxis(device_id, idx, value); + } + }); + } + + for (int i = 0; i < joy.hats.size(); i += 2) { + final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); + final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis())); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyhat(device_id, hatX, hatY); + } + }); + } + return true; + } + }; + + return false; + } + + public void initInputDevices() { + /* initially add input devices*/ + int[] deviceIds = inputManager.getInputDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice device = inputManager.getInputDevice(deviceId); + if (DEBUG) { + Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); + } + onInputDeviceAdded(deviceId); + } + } + + @Override + public void onInputDeviceAdded(int deviceId) { + int id = findJoystickDevice(deviceId); + + // Check if the device has not been already added + if (id < 0) { + InputDevice device = inputManager.getInputDevice(deviceId); + //device can be null if deviceId is not found + if (device != null) { + int sources = device.getSources(); + if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { + id = joysticksDevices.size(); + + Joystick joy = new Joystick(); + joy.device_id = deviceId; + joy.name = device.getName(); + joy.axes = new ArrayList(); + joy.hats = new ArrayList(); + + List ranges = device.getMotionRanges(); + Collections.sort(ranges, new RangeComparator()); + + for (InputDevice.MotionRange range : ranges) { + if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { + joy.hats.add(range); + } else { + joy.axes.add(range); + } + } + + joysticksDevices.add(joy); + + final int device_id = id; + final String name = joy.name; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(device_id, true, name); + } + }); + } + } + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + final int device_id = findJoystickDevice(deviceId); + + // Check if the evice has not been already removed + if (device_id > -1) { + joysticksDevices.remove(device_id); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(device_id, false, ""); + } + }); + } + } + + @Override + public void onInputDeviceChanged(int deviceId) { + onInputDeviceRemoved(deviceId); + onInputDeviceAdded(deviceId); + } + + private static class RangeComparator implements Comparator { + @Override + public int compare(MotionRange arg0, MotionRange arg1) { + return arg0.getAxis() - arg1.getAxis(); + } + } + + public static int getGodotButton(int keyCode) { + int button; + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B + button = 0; + break; + case KeyEvent.KEYCODE_BUTTON_B: + button = 1; + break; + case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y + button = 2; + break; + case KeyEvent.KEYCODE_BUTTON_Y: + button = 3; + break; + case KeyEvent.KEYCODE_BUTTON_L1: + button = 9; + break; + case KeyEvent.KEYCODE_BUTTON_L2: + button = 15; + break; + case KeyEvent.KEYCODE_BUTTON_R1: + button = 10; + break; + case KeyEvent.KEYCODE_BUTTON_R2: + button = 16; + break; + case KeyEvent.KEYCODE_BUTTON_SELECT: + button = 4; + break; + case KeyEvent.KEYCODE_BUTTON_START: + button = 6; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + button = 7; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + button = 8; + break; + case KeyEvent.KEYCODE_DPAD_UP: + button = 11; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + button = 12; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + button = 13; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + button = 14; + break; + case KeyEvent.KEYCODE_BUTTON_C: + button = 17; + break; + case KeyEvent.KEYCODE_BUTTON_Z: + button = 18; + break; + + default: + button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20; + break; + } + return button; + } + + private int findJoystickDevice(int device_id) { + for (int i = 0; i < joysticksDevices.size(); i++) { + if (joysticksDevices.get(i).device_id == device_id) { + return i; + } + } + + return -1; + } +} diff --git a/platform/android/java/src/org/godotengine/godot/input/Joystick.java b/platform/android/java/src/org/godotengine/godot/input/Joystick.java new file mode 100644 index 00000000000..ff95bfb0c54 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/input/Joystick.java @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* Joystick.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.input; + +import android.view.InputDevice.MotionRange; +import java.util.ArrayList; + +/** + * POJO class to represent a Joystick input device. + */ +class Joystick { + int device_id; + String name; + ArrayList axes; + ArrayList hats; +} diff --git a/platform/android/java/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/src/org/godotengine/godot/utils/GLUtils.java new file mode 100644 index 00000000000..6c95494f8b3 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/utils/GLUtils.java @@ -0,0 +1,157 @@ +/*************************************************************************/ +/* GLUtils.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.utils; + +import android.util.Log; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * Contains GL utilities methods. + */ +public class GLUtils { + + private static final String TAG = GLUtils.class.getSimpleName(); + + public static final boolean DEBUG = false; + + public static boolean use_gl3 = false; + public static boolean use_32 = false; + public static boolean use_debug_opengl = false; + + private static final String[] ATTRIBUTES_NAMES = new String[] { + "EGL_BUFFER_SIZE", + "EGL_ALPHA_SIZE", + "EGL_BLUE_SIZE", + "EGL_GREEN_SIZE", + "EGL_RED_SIZE", + "EGL_DEPTH_SIZE", + "EGL_STENCIL_SIZE", + "EGL_CONFIG_CAVEAT", + "EGL_CONFIG_ID", + "EGL_LEVEL", + "EGL_MAX_PBUFFER_HEIGHT", + "EGL_MAX_PBUFFER_PIXELS", + "EGL_MAX_PBUFFER_WIDTH", + "EGL_NATIVE_RENDERABLE", + "EGL_NATIVE_VISUAL_ID", + "EGL_NATIVE_VISUAL_TYPE", + "EGL_PRESERVED_RESOURCES", + "EGL_SAMPLES", + "EGL_SAMPLE_BUFFERS", + "EGL_SURFACE_TYPE", + "EGL_TRANSPARENT_TYPE", + "EGL_TRANSPARENT_RED_VALUE", + "EGL_TRANSPARENT_GREEN_VALUE", + "EGL_TRANSPARENT_BLUE_VALUE", + "EGL_BIND_TO_TEXTURE_RGB", + "EGL_BIND_TO_TEXTURE_RGBA", + "EGL_MIN_SWAP_INTERVAL", + "EGL_MAX_SWAP_INTERVAL", + "EGL_LUMINANCE_SIZE", + "EGL_ALPHA_MASK_SIZE", + "EGL_COLOR_BUFFER_TYPE", + "EGL_RENDERABLE_TYPE", + "EGL_CONFORMANT" + }; + + private static final int[] ATTRIBUTES = new int[] { + EGL10.EGL_BUFFER_SIZE, + EGL10.EGL_ALPHA_SIZE, + EGL10.EGL_BLUE_SIZE, + EGL10.EGL_GREEN_SIZE, + EGL10.EGL_RED_SIZE, + EGL10.EGL_DEPTH_SIZE, + EGL10.EGL_STENCIL_SIZE, + EGL10.EGL_CONFIG_CAVEAT, + EGL10.EGL_CONFIG_ID, + EGL10.EGL_LEVEL, + EGL10.EGL_MAX_PBUFFER_HEIGHT, + EGL10.EGL_MAX_PBUFFER_PIXELS, + EGL10.EGL_MAX_PBUFFER_WIDTH, + EGL10.EGL_NATIVE_RENDERABLE, + EGL10.EGL_NATIVE_VISUAL_ID, + EGL10.EGL_NATIVE_VISUAL_TYPE, + 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, + EGL10.EGL_SAMPLES, + EGL10.EGL_SAMPLE_BUFFERS, + EGL10.EGL_SURFACE_TYPE, + EGL10.EGL_TRANSPARENT_TYPE, + EGL10.EGL_TRANSPARENT_RED_VALUE, + EGL10.EGL_TRANSPARENT_GREEN_VALUE, + EGL10.EGL_TRANSPARENT_BLUE_VALUE, + 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, + 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, + 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, + 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, + EGL10.EGL_LUMINANCE_SIZE, + EGL10.EGL_ALPHA_MASK_SIZE, + EGL10.EGL_COLOR_BUFFER_TYPE, + EGL10.EGL_RENDERABLE_TYPE, + 0x3042 // EGL10.EGL_CONFORMANT + }; + + private GLUtils() {} + + public static void checkEglError(String tag, String prompt, EGL10 egl) { + int error; + while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { + Log.e(tag, String.format("%s: EGL error: 0x%x", prompt, error)); + } + } + + public static void printConfigs(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + int numConfigs = configs.length; + Log.v(TAG, String.format("%d configurations", numConfigs)); + for (int i = 0; i < numConfigs; i++) { + Log.v(TAG, String.format("Configuration %d:\n", i)); + printConfig(egl, display, configs[i]); + } + } + + private static void printConfig(EGL10 egl, EGLDisplay display, + EGLConfig config) { + int[] value = new int[1]; + for (int i = 0; i < ATTRIBUTES.length; i++) { + int attribute = ATTRIBUTES[i]; + String name = ATTRIBUTES_NAMES[i]; + if (egl.eglGetConfigAttrib(display, config, attribute, value)) { + Log.i(TAG, String.format(" %s: %d\n", name, value[0])); + } else { + // Log.w(TAG, String.format(" %s: failed\n", name)); + while (egl.eglGetError() != EGL10.EGL_SUCCESS) + ; + } + } + } +} diff --git a/platform/android/java/src/org/godotengine/godot/xr/XRMode.java b/platform/android/java/src/org/godotengine/godot/xr/XRMode.java new file mode 100644 index 00000000000..cbc8a1e9025 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/XRMode.java @@ -0,0 +1,39 @@ +/*************************************************************************/ +/* XRMode.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr; + +/** + * Godot available XR modes. + */ +public enum XRMode { + PANCAKE, // Regular/flatscreen + OVR, // Oculus mobile VR SDK +} diff --git a/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java new file mode 100644 index 00000000000..ff836a31ca5 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* OvrConfigChooser.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.ovr; + +import android.opengl.EGLExt; +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * EGL config chooser for the Oculus Mobile VR SDK. + */ +public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser { + + private static final int[] CONFIG_ATTRIBS = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, // Need alpha for the multi-pass timewarp compositor + EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_STENCIL_SIZE, 0, + EGL10.EGL_SAMPLES, 0, + EGL10.EGL_NONE + }; + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + // Do NOT use eglChooseConfig, because the Android EGL code pushes in + // multisample flags in eglChooseConfig if the user has selected the "force 4x + // MSAA" option in settings, and that is completely wasted for our warp + // target. + int[] numConfig = new int[1]; + if (!egl.eglGetConfigs(display, null, 0, numConfig)) { + throw new IllegalArgumentException("eglGetConfigs failed."); + } + + int configsCount = numConfig[0]; + if (configsCount <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[configsCount]; + if (!egl.eglGetConfigs(display, configs, configsCount, numConfig)) { + throw new IllegalArgumentException("eglGetConfigs #2 failed."); + } + + int[] value = new int[1]; + for (EGLConfig config : configs) { + egl.eglGetConfigAttrib(display, config, EGL10.EGL_RENDERABLE_TYPE, value); + if ((value[0] & EGLExt.EGL_OPENGL_ES3_BIT_KHR) != EGLExt.EGL_OPENGL_ES3_BIT_KHR) { + continue; + } + + // The pbuffer config also needs to be compatible with normal window rendering + // so it can share textures with the window context. + egl.eglGetConfigAttrib(display, config, EGL10.EGL_SURFACE_TYPE, value); + if ((value[0] & (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) != (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) { + continue; + } + + // Check each attribute in CONFIG_ATTRIBS (which are the attributes we care about) + // and ensure the value in config matches. + int attribIndex = 0; + while (CONFIG_ATTRIBS[attribIndex] != EGL10.EGL_NONE) { + egl.eglGetConfigAttrib(display, config, CONFIG_ATTRIBS[attribIndex], value); + if (value[0] != CONFIG_ATTRIBS[attribIndex + 1]) { + // Attribute key's value does not match the configs value. + // Start checking next config. + break; + } + + // Step by two because CONFIG_ATTRIBS is in key/value pairs. + attribIndex += 2; + } + + if (CONFIG_ATTRIBS[attribIndex] == EGL10.EGL_NONE) { + // All relevant attributes match, set the config and stop checking the rest. + return config; + } + } + return null; + } +} diff --git a/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java new file mode 100644 index 00000000000..5f6da8c6720 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* OvrContextFactory.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.ovr; + +import android.opengl.EGL14; +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * EGL Context factory for the Oculus mobile VR SDK. + */ +public class OvrContextFactory implements GLSurfaceView.EGLContextFactory { + + private static final int[] CONTEXT_ATTRIBS = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE + }; + + @Override + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + return egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, CONTEXT_ATTRIBS); + } + + @Override + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } +} diff --git a/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java new file mode 100644 index 00000000000..f1e38c35d8a --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* OvrWindowSurfaceFactory.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.ovr; + +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +/** + * EGL window surface factory for the Oculus mobile VR SDK. + */ +public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory { + + private final static int[] SURFACE_ATTRIBS = { + EGL10.EGL_WIDTH, 16, + EGL10.EGL_HEIGHT, 16, + EGL10.EGL_NONE + }; + + @Override + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, + Object nativeWindow) { + return egl.eglCreatePbufferSurface(display, config, SURFACE_ATTRIBS); + } + + @Override + public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } +} diff --git a/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java new file mode 100644 index 00000000000..ac19a09e76d --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* PancakeConfigChooser.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.pancake; + +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import org.godotengine.godot.utils.GLUtils; + +/** + * Used to select the egl config for pancake games. + */ +public class PancakeConfigChooser implements GLSurfaceView.EGLConfigChooser { + + private static final String TAG = PancakeConfigChooser.class.getSimpleName(); + + private int[] mValue = new int[1]; + + /* This EGL config specification is used to specify 2.0 rendering. + * We use a minimum size of 4 bits for red/green/blue, but will + * perform actual matching in chooseConfig() below. + */ + private static int EGL_OPENGL_ES2_BIT = 4; + private static int[] s_configAttribs2 = { + EGL10.EGL_RED_SIZE, 4, + EGL10.EGL_GREEN_SIZE, 4, + EGL10.EGL_BLUE_SIZE, 4, + // EGL10.EGL_DEPTH_SIZE, 16, + // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE + }; + private static int[] s_configAttribs3 = { + EGL10.EGL_RED_SIZE, 4, + EGL10.EGL_GREEN_SIZE, 4, + EGL10.EGL_BLUE_SIZE, 4, + // EGL10.EGL_DEPTH_SIZE, 16, + // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT + EGL10.EGL_NONE + }; + + public PancakeConfigChooser(int r, int g, int b, int a, int depth, int stencil) { + mRedSize = r; + mGreenSize = g; + mBlueSize = b; + mAlphaSize = a; + mDepthSize = depth; + mStencilSize = stencil; + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + + /* Get the number of minimally matching EGL configurations + */ + int[] num_config = new int[1]; + egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config); + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + /* Allocate then read the array of minimally matching EGL configs + */ + EGLConfig[] configs = new EGLConfig[numConfigs]; + egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config); + + if (GLUtils.DEBUG) { + GLUtils.printConfigs(egl, display, configs); + } + /* Now return the "best" one + */ + return chooseConfig(egl, display, configs); + } + + 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); + + // We need at least mDepthSize and mStencilSize bits + if (d < mDepthSize || s < mStencilSize) + continue; + + // We want an *exact* match for red/green/blue/alpha + 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; + } + + // 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/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java new file mode 100644 index 00000000000..aca6ffdba64 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* PancakeContextFactory.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.pancake; + +import android.opengl.GLSurfaceView; +import android.util.Log; +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 org.godotengine.godot.GodotLib; +import org.godotengine.godot.utils.GLUtils; + +/** + * Factory used to setup the opengl context for pancake games. + */ +public class PancakeContextFactory implements GLSurfaceView.EGLContextFactory { + private static final String TAG = PancakeContextFactory.class.getSimpleName(); + + private static final int _EGL_CONTEXT_FLAGS_KHR = 0x30FC; + private static final int _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR = 0x00000001; + + private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name"); + if (GLUtils.use_gl3 && !driver_name.equals("GLES3")) { + GLUtils.use_gl3 = false; + } + if (GLUtils.use_gl3) + Log.w(TAG, "creating OpenGL ES 3.0 context :"); + else + Log.w(TAG, "creating OpenGL ES 2.0 context :"); + + GLUtils.checkEglError(TAG, "Before eglCreateContext", egl); + EGLContext context; + if (GLUtils.use_debug_opengl) { + int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; + int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; + context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2); + } else { + int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; + int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; + context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2); + } + GLUtils.checkEglError(TAG, "After eglCreateContext", egl); + return context; + } + + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } +} diff --git a/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java new file mode 100644 index 00000000000..e19f218916a --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* PancakeFallbackConfigChooser.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.pancake; + +import android.util.Log; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import org.godotengine.godot.utils.GLUtils; + +/* Fallback if 32bit View is not supported*/ +public class PancakeFallbackConfigChooser extends PancakeConfigChooser { + + private static final String TAG = PancakeFallbackConfigChooser.class.getSimpleName(); + + private PancakeConfigChooser fallback; + + public PancakeFallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, PancakeConfigChooser fallback) { + super(r, g, b, a, depth, stencil); + this.fallback = fallback; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { + EGLConfig ec = super.chooseConfig(egl, display, configs); + if (ec == null) { + Log.w(TAG, "Trying ConfigChooser fallback"); + ec = fallback.chooseConfig(egl, display, configs); + GLUtils.use_32 = false; + } + return ec; + } +} diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index e92d4437b1f..339b14974c7 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -60,6 +60,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); + _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); + _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -191,3 +193,21 @@ void GodotJavaWrapper::init_input_devices() { env->CallVoidMethod(godot_instance, _init_input_devices); } } + +jobject GodotJavaWrapper::get_surface() { + if (_get_surface) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallObjectMethod(godot_instance, _get_surface); + } else { + return NULL; + } +} + +bool GodotJavaWrapper::is_activity_resumed() { + if (_is_activity_resumed) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallBooleanMethod(godot_instance, _is_activity_resumed); + } else { + return false; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index be4f109d8c5..82c2a5d1225 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -55,6 +55,8 @@ private: jmethodID _set_clipboard = 0; jmethodID _request_permission = 0; jmethodID _init_input_devices = 0; + jmethodID _get_surface = 0; + jmethodID _is_activity_resumed = 0; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); @@ -78,6 +80,8 @@ public: void set_clipboard(const String &p_text); bool request_permission(const String &p_name); void init_input_devices(); + jobject get_surface(); + bool is_activity_resumed(); }; #endif /* !JAVA_GODOT_WRAPPER_H */