Commit 5ccdeccb by Ruslan Mustakov

Make GDNative work on Android

The changes include work done to ensure that GDNative apps and Nim integration specifically can run on Android. The changes have been tested on our WIP game, which uses godot-nim and depends on several third-party .so libs, and Platformer demo to ensure nothing got broken. - .so libraries are exported to lib/ folder in .apk, instead of assets/, because that's where Android expects them to be and it resolves the library name into "lib/<ABI>/<name>", where <ABI> is the ABI matching the current device. So we establish the convention that Android .so files in the project must be located in the folder corresponding to the ABI they were compiled for. - Godot callbacks (event handlers) are now called from the same thread from which Main::iteration is called. It is also what Godot now considers to be the main thread, because Main::setup is also called from there. This makes threading on Android more consistent with other platforms, making the code that depends on Thread::get_main_id more portable (GDNative has such code). - Sizes of GDNative API types have been fixed to work on 32-bit platforms.
parent 8b9026c0
......@@ -1098,10 +1098,20 @@ bool Main::start() {
test = args[i + 1];
} else if (args[i] == "--export") {
editor = true; //needs editor
_export_preset = args[i + 1];
if (i + 1 < args.size()) {
_export_preset = args[i + 1];
} else {
ERR_PRINT("Export preset name not specified");
return false;
}
} else if (args[i] == "--export-debug") {
editor = true; //needs editor
_export_preset = args[i + 1];
if (i + 1 < args.size()) {
_export_preset = args[i + 1];
} else {
ERR_PRINT("Export preset name not specified");
return false;
}
export_debug = true;
} else {
// The parameter does not match anything known, don't skip the next argument
......
......@@ -40,7 +40,7 @@
const String init_symbol = "godot_gdnative_init";
const String terminate_symbol = "godot_gdnative_terminate";
String GDNativeLibrary::platform_names[NUM_PLATFORMS] = {
String GDNativeLibrary::platform_names[NUM_PLATFORMS + 1] = {
"X11_32bit",
"X11_64bit",
"Windows_32bit",
......@@ -48,11 +48,15 @@ String GDNativeLibrary::platform_names[NUM_PLATFORMS] = {
"OSX",
"Android",
"iOS",
"WebAssembly"
"iOS_32bit",
"iOS_64bit",
"WebAssembly",
""
};
String GDNativeLibrary::platform_lib_ext[NUM_PLATFORMS] = {
String GDNativeLibrary::platform_lib_ext[NUM_PLATFORMS + 1] = {
"so",
"so",
"dll",
......@@ -60,21 +64,30 @@ String GDNativeLibrary::platform_lib_ext[NUM_PLATFORMS] = {
"dylib",
"so",
"dylib",
"dylib",
"wasm",
"wasm"
""
};
// TODO(karroffel): make this actually do the right thing.
GDNativeLibrary::Platform GDNativeLibrary::current_platform =
#if defined(X11_ENABLED)
X11_64BIT;
(sizeof(void *) == 8 ? X11_64BIT : X11_32BIT);
#elif defined(WINDOWS_ENABLED)
WINDOWS_64BIT;
(sizeof(void *) == 8 ? WINDOWS_64BIT : WINDOWS_32BIT);
#elif defined(OSX_ENABLED)
OSX;
#elif defined(IPHONE_ENABLED)
(sizeof(void *) == 8 ? IOS_64BIT : IOS_32BIT);
#elif defined(ANDROID_ENABLED)
ANDROID;
#elif defined(JAVASCRIPT_ENABLED)
WASM;
#else
X11_64BIT; // need a sensible default..
NUM_PLATFORMS;
#endif
GDNativeLibrary::GDNativeLibrary()
......@@ -151,7 +164,10 @@ String GDNativeLibrary::get_library_path(StringName p_platform) const {
}
String GDNativeLibrary::get_active_library_path() const {
return library_paths[GDNativeLibrary::current_platform];
if (GDNativeLibrary::current_platform != NUM_PLATFORMS) {
return library_paths[GDNativeLibrary::current_platform];
}
return "";
}
GDNative::GDNative() {
......
......@@ -48,11 +48,17 @@ class GDNativeLibrary : public Resource {
// NOTE(karroffel): I heard OSX 32 bit is dead, so 64 only
OSX,
// TODO(karroffel): all different android versions and archs
// Android .so files must be located in directories corresponding to Android ABI names:
// https://developer.android.com/ndk/guides/abis.html
// Android runtime will select the matching library depending on the device.
// The value here must simply point to the .so name, for example:
// "res://libmy_gdnative.so" or "libmy_gdnative.so",
// while in the project the actual paths can be "lib/android/armeabi-v7a/libmy_gdnative.so",
// "lib/android/arm64-v8a/libmy_gdnative.so".
ANDROID,
// TODO(karroffe): all different iOS versions and archs
IOS,
IOS_32BIT,
IOS_64BIT,
// TODO(karroffel): figure out how to deal with web stuff at all...
WASM,
......@@ -64,10 +70,9 @@ class GDNativeLibrary : public Resource {
};
static String platform_names[NUM_PLATFORMS];
static String platform_lib_ext[NUM_PLATFORMS];
static String platform_names[NUM_PLATFORMS + 1];
static String platform_lib_ext[NUM_PLATFORMS + 1];
// TODO(karroffel): make this actually do something lol.
static Platform current_platform;
String library_paths[NUM_PLATFORMS];
......
......@@ -37,7 +37,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_ARRAY_SIZE 8
#define GODOT_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_ARRAY_TYPE_DEFINED
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_DICTIONARY_SIZE 8
#define GODOT_DICTIONARY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_DICTIONARY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_DICTIONARY_TYPE_DEFINED
......
......@@ -56,7 +56,7 @@ extern "C" {
#define GDAPI GDCALLINGCONV
#endif
#else
#define GDCALLINGCONV __attribute__((sysv_abi))
#define GDCALLINGCONV __attribute__((sysv_abi, visibility("default")))
#define GDAPI GDCALLINGCONV
#endif
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_NODE_PATH_SIZE 8
#define GODOT_NODE_PATH_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_NODE_PATH_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_NODE_PATH_TYPE_DEFINED
......
......@@ -38,7 +38,7 @@ extern "C" {
/////// PoolByteArray
#define GODOT_POOL_BYTE_ARRAY_SIZE 8
#define GODOT_POOL_BYTE_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_BYTE_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_BYTE_ARRAY_TYPE_DEFINED
......@@ -49,7 +49,7 @@ typedef struct {
/////// PoolIntArray
#define GODOT_POOL_INT_ARRAY_SIZE 8
#define GODOT_POOL_INT_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_INT_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_INT_ARRAY_TYPE_DEFINED
......@@ -60,7 +60,7 @@ typedef struct {
/////// PoolRealArray
#define GODOT_POOL_REAL_ARRAY_SIZE 8
#define GODOT_POOL_REAL_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_REAL_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_REAL_ARRAY_TYPE_DEFINED
......@@ -71,7 +71,7 @@ typedef struct {
/////// PoolStringArray
#define GODOT_POOL_STRING_ARRAY_SIZE 8
#define GODOT_POOL_STRING_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_STRING_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_STRING_ARRAY_TYPE_DEFINED
......@@ -82,7 +82,7 @@ typedef struct {
/////// PoolVector2Array
#define GODOT_POOL_VECTOR2_ARRAY_SIZE 8
#define GODOT_POOL_VECTOR2_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_VECTOR2_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_VECTOR2_ARRAY_TYPE_DEFINED
......@@ -93,7 +93,7 @@ typedef struct {
/////// PoolVector3Array
#define GODOT_POOL_VECTOR3_ARRAY_SIZE 8
#define GODOT_POOL_VECTOR3_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_VECTOR3_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_VECTOR3_ARRAY_TYPE_DEFINED
......@@ -104,7 +104,7 @@ typedef struct {
/////// PoolColorArray
#define GODOT_POOL_COLOR_ARRAY_SIZE 8
#define GODOT_POOL_COLOR_ARRAY_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_POOL_COLOR_ARRAY_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_POOL_COLOR_ARRAY_TYPE_DEFINED
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_RID_SIZE 8
#define GODOT_RID_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_RID_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_RID_TYPE_DEFINED
......
......@@ -37,7 +37,7 @@ extern "C" {
#include <stdint.h>
#include <wchar.h>
#define GODOT_STRING_SIZE 8
#define GODOT_STRING_SIZE sizeof(void *)
#ifndef GODOT_CORE_API_GODOT_STRING_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_STRING_TYPE_DEFINED
......
......@@ -36,7 +36,7 @@ extern "C" {
#include <stdint.h>
#define GODOT_VARIANT_SIZE 24
#define GODOT_VARIANT_SIZE (16 + sizeof(void *))
#ifndef GODOT_CORE_API_GODOT_VARIANT_TYPE_DEFINED
#define GODOT_CORE_API_GODOT_VARIANT_TYPE_DEFINED
......
......@@ -58,7 +58,7 @@ extern "C" {
#define GDAPI GDCALLINGCONV
#endif
#else
#define GDCALLINGCONV __attribute__((sysv_abi))
#define GDCALLINGCONV __attribute__((sysv_abi, visibility("default")))
#define GDAPI GDCALLINGCONV
#endif
......
......@@ -994,6 +994,8 @@ void NativeScriptLanguage::init_library(const Ref<GDNativeLibrary> &lib) {
#endif
// See if this library was "registered" already.
const String &lib_path = lib->get_active_library_path();
ERR_EXPLAIN(lib->get_name() + " does not have a library for the current platform");
ERR_FAIL_COND(lib_path.length() == 0);
Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
if (!E) {
......
......@@ -240,7 +240,7 @@ def configure(env):
env.Append(CPPPATH=['#platform/android'])
env.Append(CPPFLAGS=['-DANDROID_ENABLED', '-DUNIX_ENABLED', '-DNO_FCNTL', '-DMPC_FIXED_POINT'])
env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'android', 'log', 'z'])
env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'android', 'log', 'z', 'dl'])
# TODO: Move that to opus module's config
if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"):
......
......@@ -468,11 +468,36 @@ class EditorExportAndroid : public EditorExportPlatform {
return zipfi;
}
static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
static Set<String> get_abis() {
Set<String> abis;
abis.insert("armeabi");
abis.insert("armeabi-v7a");
abis.insert("arm64-v8a");
abis.insert("x86");
abis.insert("x86_64");
abis.insert("mips");
abis.insert("mips64");
return abis;
}
static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) {
APKExportData *ed = (APKExportData *)p_userdata;
String dst_path = p_path;
dst_path = dst_path.replace_first("res://", "assets/");
static Set<String> android_abis = get_abis();
if (dst_path.ends_with(".so")) {
String abi = dst_path.get_base_dir().get_file().strip_edges(); // parent dir name
if (android_abis.has(abi)) {
dst_path = "lib/" + abi + "/" + dst_path.get_file();
} else {
String err = "Dynamic libraries must be located in the folder named after Android ABI they were compiled for. " +
p_path + " does not follow this convention.";
ERR_PRINT(err.utf8().get_data());
return ERR_FILE_BAD_PATH;
}
} else {
dst_path = dst_path.replace_first("res://", "assets/");
}
zip_fileinfo zipfi = get_zip_fileinfo();
......@@ -1375,15 +1400,15 @@ public:
}
}
if (file == "lib/x86/libgodot_android.so" && !export_x86) {
if (file == "lib/x86/*.so" && !export_x86) {
skip = true;
}
if (file.match("lib/armeabi*/libgodot_android.so") && !export_arm) {
if (file.match("lib/armeabi*/*.so") && !export_arm) {
skip = true;
}
if (file.match("lib/arm64*/libgodot_android.so") && !export_arm64) {
if (file.match("lib/arm64*/*.so") && !export_arm64) {
skip = true;
}
......
......@@ -37,7 +37,7 @@ public class GodotLib {
public static GodotIO io;
static {
System.loadLibrary("godot_android");
System.loadLibrary("godot_android");
}
/**
......@@ -45,7 +45,8 @@ public class GodotLib {
* @param height the current view height
*/
public static native void initialize(Godot p_instance,boolean need_reload_hook,String[] p_cmdline,Object p_asset_manager);
public static native void initialize(Godot p_instance,boolean need_reload_hook,Object p_asset_manager, boolean use_apk_expansion);
public static native void setup(String[] p_cmdline);
public static native void resize(int width, int height,boolean reload);
public static native void newcontext(boolean p_32_bits);
public static native void back();
......
......@@ -89,7 +89,7 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
}
public void callbackSuccess(String ticket, String signature, String sku) {
GodotLib.callobject(purchaseCallbackId, "purchase_success", new Object[]{ticket, signature, sku});
GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[]{ticket, signature, sku});
}
public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) {
......
......@@ -208,8 +208,9 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
@Override public void onInputDeviceAdded(int deviceId) {
joystick joy = new joystick();
joy.device_id = deviceId;
int id = joy_devices.size();
final int id = joy_devices.size();
InputDevice device = mInputManager.getInputDevice(deviceId);
final String name = device.getName();
joy.name = device.getName();
joy.axes = new ArrayList<InputDevice.MotionRange>();
joy.hats = new ArrayList<InputDevice.MotionRange>();
......@@ -224,19 +225,29 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
}
}
joy_devices.add(joy);
GodotLib.joyconnectionchanged(id, true, joy.name);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyconnectionchanged(id, true, name);
}
});
}
@Override public void onInputDeviceRemoved(int deviceId) {
int id = find_joy_device(deviceId);
final int id = find_joy_device(deviceId);
joy_devices.remove(id);
GodotLib.joyconnectionchanged(id, false, "");
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyconnectionchanged(id, false, "");
}
});
}
@Override public void onInputDeviceChanged(int deviceId) {
}
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
@Override public boolean onKeyUp(final int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return true;
......@@ -249,22 +260,38 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
int source = event.getSource();
if ((source & InputDevice.SOURCE_JOYSTICK) != 0 || (source & InputDevice.SOURCE_DPAD) != 0 || (source & InputDevice.SOURCE_GAMEPAD) != 0) {
int button = get_godot_button(keyCode);
int device = find_joy_device(event.getDeviceId());
final int button = get_godot_button(keyCode);
final int device = find_joy_device(event.getDeviceId());
GodotLib.joybutton(device, button, false);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joybutton(device, button, false);
}
});
return true;
} else {
GodotLib.key(keyCode, event.getUnicodeChar(0), false);
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(keyCode, chr, false);
}
});
};
return super.onKeyUp(keyCode, event);
};
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
@Override public boolean onKeyDown(final int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
GodotLib.back();
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.back();
}
});
// press 'back' button should not terminate program
//normal handle 'back' event in game logic
return true;
......@@ -281,16 +308,26 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
if (event.getRepeatCount() > 0) // ignore key echo
return true;
int button = get_godot_button(keyCode);
int device = find_joy_device(event.getDeviceId());
final int button = get_godot_button(keyCode);
final int device = find_joy_device(event.getDeviceId());
//Log.e(TAG, String.format("joy button down! button %x, %d, device %d", keyCode, button, device));
GodotLib.joybutton(device, button, true);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joybutton(device, button, true);
}
});
return true;
} else {
GodotLib.key(keyCode, event.getUnicodeChar(0), true);
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(keyCode, chr, true);
}
});
};
return super.onKeyDown(keyCode, event);
}
......@@ -299,21 +336,32 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
int device_id = find_joy_device(event.getDeviceId());
final int device_id = find_joy_device(event.getDeviceId());
joystick joy = joy_devices.get(device_id);
for (int i = 0; i < joy.axes.size(); i++) {
InputDevice.MotionRange range = joy.axes.get(i);
float value = (event.getAxisValue(range.getAxis()) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
final float value = (event.getAxisValue(range.getAxis()) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
//Log.e(TAG, String.format("axis event: %d, value %f", i, value));
GodotLib.joyaxis(device_id, i, value);
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) {
int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
int hatY = Math.round(event.getAxisValue(joy.hats.get(i+1).getAxis()));
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()));
//Log.e(TAG, String.format("HAT EVENT %d, %d", hatX, hatY));
GodotLib.joyhat(device_id, hatX, hatY);
queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.joyhat(device_id, hatX, hatY);
}
});
}
return true;
};
......
......@@ -89,8 +89,13 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
//Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after);
for (int i=0;i<count;i++){
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
}
});
}
}
......@@ -99,9 +104,14 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
//Log.d(TAG, "onTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",before: " + before);
for (int i=start;i<start+count;i++){
int ch = pCharSequence.charAt(i);
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
final int ch = pCharSequence.charAt(i);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
}
});
}
}
......@@ -111,8 +121,14 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
if (this.mEdit == pTextView && this.isFullScreenEdit()) {
// user press the action button, delete all old text and insert new text
for (int i = this.mOriginText.length(); i > 0; i--) {
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
}
});
/*
if (BuildConfig.DEBUG) {
Log.d(TAG, "deleteBackward");
......@@ -131,9 +147,14 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
}
for(int i = 0; i < text.length(); i++) {
int ch = text.codePointAt(i);
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
final int ch = text.codePointAt(i);
mView.queueEvent(new Runnable() {
@Override
public void run() {
GodotLib.key(0, ch, true);
GodotLib.key(0, ch, false);
}
});
}
/*
if (BuildConfig.DEBUG) {
......@@ -141,7 +162,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
}
*/
}
if (pActionID == EditorInfo.IME_ACTION_DONE) {
this.mView.requestFocus();
}
......
......@@ -36,7 +36,8 @@
#include <jni.h>
extern "C" {
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jboolean p_need_reload_hook, jobjectArray p_cmdline, jobject p_asset_manager);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jboolean p_need_reload_hook, jobject p_asset_manager, jboolean p_use_apk_expansion);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height, jboolean reload);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment