diff --git a/common/backends.h b/common/backends.h index 6fd51f19..77bcced8 100644 --- a/common/backends.h +++ b/common/backends.h @@ -18,17 +18,21 @@ GNU General Public License for more details. // video backends (XASH_VIDEO) #define VIDEO_NULL 0 #define VIDEO_SDL 1 +#define VIDEO_ANDROID 2 #define VIDEO_FBDEV 3 #define VIDEO_DOS 4 + // audio backends (XASH_SOUND) #define SOUND_NULL 0 #define SOUND_SDL 1 +#define SOUND_OPENSLES 2 #define SOUND_ALSA 3 // input (XASH_INPUT) #define INPUT_NULL 0 #define INPUT_SDL 1 +#define INPUT_ANDROID 2 #define INPUT_EVDEV 3 // timer (XASH_TIMER) @@ -41,13 +45,16 @@ GNU General Public License for more details. // messageboxes (XASH_MESSAGEBOX) #define MSGBOX_STDERR 0 #define MSGBOX_SDL 1 +#define MSGBOX_ANDROID 2 #define MSGBOX_WIN32 3 #define MSGBOX_NSWITCH 4 + // library loading (XASH_LIB) #define LIB_NULL 0 #define LIB_POSIX 1 #define LIB_WIN32 2 #define LIB_STATIC 3 + #endif /* BACKENDS_H */ diff --git a/common/defaults.h b/common/defaults.h index cbcbd13e..f870e389 100644 --- a/common/defaults.h +++ b/common/defaults.h @@ -52,6 +52,26 @@ SETUP BACKENDS DEFINITIONS #endif #endif // XASH_MESSAGEBOX #endif + #elif XASH_ANDROID + // we are building for Android platform, use Android APIs + #ifndef XASH_VIDEO + #define XASH_VIDEO VIDEO_ANDROID + #endif // XASH_VIDEO + + #ifndef XASH_INPUT + #define XASH_INPUT INPUT_ANDROID + #endif // XASH_INPUT + + #ifndef XASH_SOUND + #define XASH_SOUND SOUND_OPENSLES + #endif // XASH_SOUND + + #ifndef XASH_MESSAGEBOX + #define XASH_MESSAGEBOX MSGBOX_ANDROID + #endif // XASH_MESSAGEBOX + + #define XASH_USE_EVDEV 1 + #define XASH_DYNAMIC_DLADDR #elif XASH_LINUX // we are building for Linux without SDL2, can draw only to framebuffer yet #ifndef XASH_VIDEO @@ -151,8 +171,6 @@ Default build-depended cvar and constant values #define DEFAULT_MODE_WIDTH 960 #define DEFAULT_MODE_HEIGHT 544 #define DEFAULT_ALLOWCONSOLE 1 -#elif XASH_ANDROID - #define DEFAULT_TOUCH_ENABLE "1" #elif XASH_MOBILE_PLATFORM #define DEFAULT_TOUCH_ENABLE "1" #define DEFAULT_M_IGNORE "1" diff --git a/engine/platform/android/android.c b/engine/platform/android/android.c index 7bba5ca5..de632382 100644 --- a/engine/platform/android/android.c +++ b/engine/platform/android/android.c @@ -14,7 +14,7 @@ GNU General Public License for more details. */ #include "platform/platform.h" -#if !defined(XASH_DEDICATED) +#if !defined(XASH_DEDICATED) && XASH_SDL #include "input.h" #include "client.h" diff --git a/engine/platform/android/android_nosdl.c b/engine/platform/android/android_nosdl.c new file mode 100644 index 00000000..6d32b62f --- /dev/null +++ b/engine/platform/android/android_nosdl.c @@ -0,0 +1,997 @@ +/* +android_nosdl.c - android backend +Copyright (C) 2016-2019 mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "platform/platform.h" +#if !defined(XASH_DEDICATED) && !XASH_SDL +#include "input.h" +#include "client.h" +#include "sound.h" +#include "platform/android/android_priv.h" +#include "errno.h" +#include +#include + +#ifndef JNICALL +#define JNICALL // a1ba: workaround for my IDE, where Java files are not included +#define JNIEXPORT +#endif + +convar_t *android_sleep; + +static const int s_android_scantokey[] = +{ + 0, K_LEFTARROW, K_RIGHTARROW, K_AUX26, K_ESCAPE, // 0 + K_AUX26, K_AUX25, '0', '1', '2', // 5 + '3', '4', '5', '6', '7', // 10 + '8', '9', '*', '#', K_UPARROW, // 15 + K_DOWNARROW, K_LEFTARROW, K_RIGHTARROW, K_ENTER, K_AUX32, // 20 + K_AUX31, K_AUX29, K_AUX28, K_AUX27, 'a', // 25 + 'b', 'c', 'd', 'e', 'f', // 30 + 'g', 'h', 'i', 'j', 'k', // 35 + 'l', 'm', 'n', 'o', 'p', // 40 + 'q', 'r', 's', 't', 'u', // 45 + 'v', 'w', 'x', 'y', 'z', // 50 + ',', '.', K_ALT, K_ALT, K_SHIFT, // 55 + K_SHIFT, K_TAB, K_SPACE, 0, 0, // 60 + 0, K_ENTER, K_BACKSPACE, '`', '-', // 65 + '=', '[', ']', '\\', ';', // 70 + '\'', '/', '@', K_KP_NUMLOCK, 0, // 75 + 0, '+', '`', 0, 0, // 80 + 0, 0, 0, 0, 0, // 85 + 0, 0, K_PGUP, K_PGDN, 0, // 90 + 0, K_AUX1, K_AUX2, K_AUX14, K_AUX3, // 95 + K_AUX4, K_AUX15, K_AUX6, K_AUX7, K_JOY1, // 100 + K_JOY2, K_AUX10, K_AUX11, K_ESCAPE, K_ESCAPE, // 105 + 0, K_ESCAPE, K_DEL, K_CTRL, K_CTRL, // 110 + K_CAPSLOCK, 0, 0, 0, 0, // 115 + 0, K_PAUSE, K_HOME, K_END, K_INS, // 120 + 0, 0, 0, 0, 0, // 125 + 0, K_F1, K_F2, K_F3, K_F4, // 130 + K_F5, K_F6, K_F7, K_F8, K_F9, // 135 + K_F10, K_F11, K_F12, K_KP_NUMLOCK, K_KP_INS, // 140 + K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5, // 145 + K_KP_RIGHTARROW,K_KP_HOME, K_KP_UPARROW, K_KP_PGUP, K_KP_SLASH, // 150 + 0, K_KP_MINUS, K_KP_PLUS, K_KP_DEL, ',', // 155 + K_KP_ENTER, '=', '(', ')' +}; + +#define ANDROID_MAX_EVENTS 64 +#define MAX_FINGERS 10 + +typedef enum event_type +{ + event_touch_down = 0, + event_touch_up, + event_touch_move, // compatible with touchEventType + event_key_down, + event_key_up, + event_set_pause, + event_resize, + event_joyhat, + event_joyball, + event_joybutton, + event_joyaxis, + event_joyadd, + event_joyremove, + event_onpause, + event_ondestroy, + event_onresume, + event_onfocuschange, +} eventtype_t; + +typedef struct touchevent_s +{ + float x; + float y; + float dx; + float dy; +} touchevent_t; + +typedef struct joyball_s +{ + short xrel; + short yrel; + byte ball; +} joyball_t; + +typedef struct joyhat_s +{ + byte hat; + byte key; +} joyhat_t; + +typedef struct joyaxis_s +{ + short val; + byte axis; +} joyaxis_t; + +typedef struct joybutton_s +{ + int down; + byte button; +} joybutton_t; + +typedef struct keyevent_s +{ + int code; +} keyevent_t; + +typedef struct event_s +{ + eventtype_t type; + int arg; + union + { + touchevent_t touch; + joyhat_t hat; + joyball_t ball; + joyaxis_t axis; + joybutton_t button; + keyevent_t key; + }; +} event_t; + +typedef struct finger_s +{ + float x, y; + qboolean down; +} finger_t; + +static struct { + pthread_mutex_t mutex; // this mutex is locked while not running frame, used for events synchronization + pthread_mutex_t framemutex; // this mutex is locked while engine is running and unlocked while it reading events, used for pause in background. + event_t queue[ANDROID_MAX_EVENTS]; + volatile int count; + finger_t fingers[MAX_FINGERS]; + char inputtext[256]; + float mousex, mousey; +} events = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; + +struct jnimethods_s jni; +struct jnimouse_s jnimouse; + +#define Android_Lock() pthread_mutex_lock(&events.mutex); +#define Android_Unlock() pthread_mutex_unlock(&events.mutex); +#define Android_PushEvent() Android_Unlock() + +typedef void (*pfnChangeGame)( const char *progname ); +int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); + +/* +======================== +Android_AllocEvent + +Lock event queue and return pointer to next event. +Caller must do Android_PushEvent() to unlock queue after setting parameters. +======================== +*/ +event_t *Android_AllocEvent( void ) +{ + Android_Lock(); + if( events.count == ANDROID_MAX_EVENTS ) + { + events.count--; //override last event + __android_log_print( ANDROID_LOG_ERROR, "Xash", "Too many events!!!" ); + } + return &events.queue[ events.count++ ]; +} + + +/* +===================================================== +JNI callbacks + +On application start, setenv and onNativeResize called from +ui thread to set up engine configuration +nativeInit called directly from engine thread and will not return until exit. +These functions may be called from other threads at any time: +nativeKey +nativeTouch +onNativeResize +nativeString +nativeSetPause +===================================================== +*/ +#define VA_ARGS(...) , ##__VA_ARGS__ // GCC extension +#define DECLARE_JNI_INTERFACE( ret, name, ... ) \ + JNIEXPORT ret JNICALL Java_su_xash_engine_XashActivity_##name( JNIEnv *env, jclass clazz VA_ARGS(__VA_ARGS__) ) + +DECLARE_JNI_INTERFACE( int, nativeInit, jobject array ) +{ + int i; + int argc; + int status; + /* Prepare the arguments. */ + + int len = (*env)->GetArrayLength(env, array); + char** argv = calloc( 1 + len + 1, sizeof( char ** )); + argc = 0; + argv[argc++] = strdup("app_process"); + for (i = 0; i < len; ++i) { + const char* utf; + char* arg = NULL; + jstring string = (*env)->GetObjectArrayElement(env, array, i); + if (string) { + utf = (*env)->GetStringUTFChars(env, string, 0); + if (utf) { + arg = strdup(utf); + (*env)->ReleaseStringUTFChars(env, string, utf); + } + (*env)->DeleteLocalRef(env, string); + } + if (!arg) { + arg = strdup(""); + } + argv[argc++] = arg; + } + argv[argc] = NULL; + prctl(PR_SET_DUMPABLE, 1); + + /* Init callbacks. */ + + jni.env = env; + jni.actcls = (*env)->FindClass(env, "su/xash/engine/XashActivity"); + jni.enableTextInput = (*env)->GetStaticMethodID(env, jni.actcls, "showKeyboard", "(I)V"); + jni.vibrate = (*env)->GetStaticMethodID(env, jni.actcls, "vibrate", "(I)V" ); + jni.messageBox = (*env)->GetStaticMethodID(env, jni.actcls, "messageBox", "(Ljava/lang/String;Ljava/lang/String;)V"); + jni.notify = (*env)->GetStaticMethodID(env, jni.actcls, "engineThreadNotify", "()V"); + jni.setTitle = (*env)->GetStaticMethodID(env, jni.actcls, "setTitle", "(Ljava/lang/String;)V"); + jni.setIcon = (*env)->GetStaticMethodID(env, jni.actcls, "setIcon", "(Ljava/lang/String;)V"); + jni.getAndroidId = (*env)->GetStaticMethodID(env, jni.actcls, "getAndroidID", "()Ljava/lang/String;"); + jni.saveID = (*env)->GetStaticMethodID(env, jni.actcls, "saveID", "(Ljava/lang/String;)V"); + jni.loadID = (*env)->GetStaticMethodID(env, jni.actcls, "loadID", "()Ljava/lang/String;"); + jni.showMouse = (*env)->GetStaticMethodID(env, jni.actcls, "showMouse", "(I)V"); + jni.shellExecute = (*env)->GetStaticMethodID(env, jni.actcls, "shellExecute", "(Ljava/lang/String;)V"); + + jni.swapBuffers = (*env)->GetStaticMethodID(env, jni.actcls, "swapBuffers", "()V"); + jni.toggleEGL = (*env)->GetStaticMethodID(env, jni.actcls, "toggleEGL", "(I)V"); + jni.createGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "createGLContext", "([I[I)Z"); + jni.getGLAttribute = (*env)->GetStaticMethodID(env, jni.actcls, "getGLAttribute", "(I)I"); + jni.deleteGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "deleteGLContext", "()Z"); + jni.getSelectedPixelFormat = (*env)->GetStaticMethodID(env, jni.actcls, "getSelectedPixelFormat", "()I"); + jni.getSurface = (*env)->GetStaticMethodID(env, jni.actcls, "getNativeSurface", "()Landroid/view/Surface;"); + + /* Run the application. */ + + status = Host_Main( argc, argv, getenv("XASH3D_GAMEDIR"), false, NULL ); + + /* Release the arguments. */ + + for (i = 0; i < argc; ++i) + free(argv[i]); + free(argv); + + return status; +} + +DECLARE_JNI_INTERFACE( void, onNativeResize, jint width, jint height ) +{ + event_t *event; + + if( !width || !height ) + return; + + jni.width=width, jni.height=height; + + // alloc update event to change screen size + event = Android_AllocEvent(); + event->type = event_resize; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeQuit ) +{ +} + +DECLARE_JNI_INTERFACE( void, nativeSetPause, jint pause ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_set_pause; + event->arg = pause; + Android_PushEvent(); + + // if pause enabled, hold engine by locking frame mutex. + // Engine will stop after event reading and will not continue untill unlock + if( android_sleep && android_sleep->value ) + { + if( pause ) + pthread_mutex_lock( &events.framemutex ); + else + pthread_mutex_unlock( &events.framemutex ); + } +} + +DECLARE_JNI_INTERFACE( void, nativeUnPause ) +{ + // UnPause engine before sending critical events + if( android_sleep && android_sleep->value ) + pthread_mutex_unlock( &events.framemutex ); +} + +DECLARE_JNI_INTERFACE( void, nativeKey, jint down, jint code ) +{ + event_t *event; + + if( code < 0 ) + { + event = Android_AllocEvent(); + event->arg = (-code) & 255; + event->type = down?event_key_down:event_key_up; + Android_PushEvent(); + } + else + { + if( code >= ( sizeof( s_android_scantokey ) / sizeof( s_android_scantokey[0] ) ) ) + { + Con_DPrintf( "nativeKey: unknown Android key %d\n", code ); + return; + } + + if( !s_android_scantokey[code] ) + { + Con_DPrintf( "nativeKey: unmapped Android key %d\n", code ); + return; + } + + event = Android_AllocEvent(); + event->type = down?event_key_down:event_key_up; + event->arg = s_android_scantokey[code]; + Android_PushEvent(); + } +} + +DECLARE_JNI_INTERFACE( void, nativeString, jobject string ) +{ + char* str = (char *) (*env)->GetStringUTFChars(env, string, NULL); + + Android_Lock(); + strncat( events.inputtext, str, 256 ); + Android_Unlock(); + + (*env)->ReleaseStringUTFChars(env, string, str); +} + +#ifdef SOFTFP_LINK +DECLARE_JNI_INTERFACE( void, nativeTouch, jint finger, jint action, jfloat x, jfloat y ) __attribute__((pcs("aapcs"))); +#endif +DECLARE_JNI_INTERFACE( void, nativeTouch, jint finger, jint action, jfloat x, jfloat y ) +{ + float dx, dy; + event_t *event; + + // if something wrong with android event + if( finger > MAX_FINGERS ) + return; + + // not touch action? + if( !( action >=0 && action <= 2 ) ) + return; + + // 0.0f .. 1.0f + x /= jni.width; + y /= jni.height; + + if( action ) + dx = x - events.fingers[finger].x, dy = y - events.fingers[finger].y; + else + dx = dy = 0.0f; + events.fingers[finger].x = x, events.fingers[finger].y = y; + + // check if we should skip some events + if( ( action == 2 ) && ( !dx && !dy ) ) + return; + + if( ( action == 0 ) && events.fingers[finger].down ) + return; + + if( ( action == 1 ) && !events.fingers[finger].down ) + return; + + if( action == 2 && !events.fingers[finger].down ) + action = 0; + + if( action == 0 ) + events.fingers[finger].down = true; + else if( action == 1 ) + events.fingers[finger].down = false; + + event = Android_AllocEvent(); + event->arg = finger; + event->type = action; + event->touch.x = x; + event->touch.y = y; + event->touch.dx = dx; + event->touch.dy = dy; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeBall, jint id, jbyte ball, jshort xrel, jshort yrel ) +{ + event_t *event = Android_AllocEvent(); + + event->type = event_joyball; + event->arg = id; + event->ball.ball = ball; + event->ball.xrel = xrel; + event->ball.yrel = yrel; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeHat, jint id, jbyte hat, jbyte key, jboolean down ) +{ + event_t *event = Android_AllocEvent(); + static byte engineKeys; + + if( !key ) + engineKeys = 0; // centered; + + if( down ) + engineKeys |= key; + else + engineKeys &= ~key; + + event->type = event_joyhat; + event->arg = id; + event->hat.hat = hat; + event->hat.key = engineKeys; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeAxis, jint id, jbyte axis, jshort val ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_joyaxis; + event->arg = id; + event->axis.axis = axis; + event->axis.val = val; + + __android_log_print(ANDROID_LOG_VERBOSE, "Xash", "axis %i %i", axis, val ); + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeJoyButton, jint id, jbyte button, jboolean down ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_joybutton; + event->arg = id; + event->button.button = button; + event->button.down = down; + __android_log_print(ANDROID_LOG_VERBOSE, "Xash", "button %i", button ); + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeJoyAdd, jint id ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_joyadd; + event->arg = id; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeJoyDel, jint id ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_joyremove; + event->arg = id; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeOnResume ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_onresume; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeOnFocusChange ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_onfocuschange; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeOnPause ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_onpause; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( void, nativeOnDestroy ) +{ + event_t *event = Android_AllocEvent(); + event->type = event_ondestroy; + Android_PushEvent(); +} + +DECLARE_JNI_INTERFACE( int, setenv, jstring key, jstring value, jboolean overwrite ) +{ + char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL); + char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL); + int err = setenv(k, v, overwrite); + (*env)->ReleaseStringUTFChars(env, key, k); + (*env)->ReleaseStringUTFChars(env, value, v); + return err; +} + + +DECLARE_JNI_INTERFACE( void, nativeMouseMove, jfloat x, jfloat y ) +{ + Android_Lock(); + events.mousex += x; + events.mousey += y; + Android_Unlock(); +} + +DECLARE_JNI_INTERFACE( int, nativeTestWritePermission, jstring jPath ) +{ + char *path = (char *)(*env)->GetStringUTFChars(env, jPath, NULL); + FILE *fd; + char testFile[PATH_MAX]; + int ret = 0; + + // maybe generate new file everytime? + Q_snprintf( testFile, PATH_MAX, "%s/.testfile", path ); + + __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: file=%s", testFile ); + + fd = fopen( testFile, "w+" ); + + if( fd ) + { + __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: passed" ); + ret = 1; + fclose( fd ); + + remove( testFile ); + } + else + { + __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: error=%s", strerror( errno ) ); + } + + (*env)->ReleaseStringUTFChars( env, jPath, path ); + + return ret; +} + +JNIEXPORT jint JNICALL JNI_OnLoad( JavaVM *vm, void *reserved ) +{ + return JNI_VERSION_1_6; +} + +/* +======================== +Platform_Init + +Initialize android-related cvars +======================== +*/ +void Platform_Init( void ) +{ + android_sleep = Cvar_Get( "android_sleep", "1", FCVAR_ARCHIVE, "Enable sleep in background" ); +} + +void Platform_Shutdown( void ) +{ + +} + +/* +======================== +Android_EnableTextInput + +Show virtual keyboard +======================== +*/ +void Platform_EnableTextInput( qboolean enable ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.enableTextInput, enable ); +} + +/* +======================== +Android_Vibrate +======================== +*/ +void Platform_Vibrate( float life, char flags ) +{ + if( life ) + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.vibrate, (int)life ); +} + +/* +======================== +Android_GetNativeObject +======================== +*/ +void *Platform_GetNativeObject( const char *objName ) +{ + static const char *availObjects[] = { "JNIEnv", "ActivityClass", NULL }; + void *object = NULL; + + if( !objName ) + { + object = (void*)availObjects; + } + else if( !strcasecmp( objName, "JNIEnv" ) ) + { + object = (void*)jni.env; + } + else if( !strcasecmp( objName, "ActivityClass" ) ) + { + object = (void*)jni.actcls; + } + + return object; +} + +/* +======================== +Android_MessageBox + +Show messagebox and wait for OK button press +======================== +*/ +#if XASH_MESSAGEBOX == MSGBOX_ANDROID +void Platform_MessageBox( const char *title, const char *text, qboolean parentMainWindow ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.messageBox, (*jni.env)->NewStringUTF( jni.env, title ), (*jni.env)->NewStringUTF( jni.env ,text ) ); +} +#endif // XASH_MESSAGEBOX == MSGBOX_ANDROID + +/* +======================== +Android_GetAndroidID +======================== +*/ +const char *Android_GetAndroidID( void ) +{ + static char id[65]; + const char *resultCStr; + jstring resultJNIStr; + + if( id[0] ) + return id; + + resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.getAndroidId ); + resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); + Q_strncpy( id, resultCStr, 64 ); + (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); + + if( !id[0] ) + return NULL; + + return id; +} + +/* +======================== +Android_LoadID +======================== +*/ +const char *Android_LoadID( void ) +{ + static char id[65]; + jstring resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.loadID ); + const char *resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); + Q_strncpy( id, resultCStr, 64 ); + (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); + return id; +} + +/* +======================== +Android_SaveID +======================== +*/ +void Android_SaveID( const char *id ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.saveID, (*jni.env)->NewStringUTF( jni.env, id ) ); +} + +/* +======================== +Android_MouseMove +======================== +*/ +void Platform_MouseMove( float *x, float *y ) +{ + *x = jnimouse.x; + *y = jnimouse.y; + jnimouse.x = 0; + jnimouse.y = 0; + // Con_Reportf( "Android_MouseMove: %f %f\n", *x, *y ); +} + +/* +======================== +Android_AddMove +======================== +*/ +void Android_AddMove( float x, float y ) +{ + jnimouse.x += x; + jnimouse.y += y; +} + +void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) +{ + // stub +} + +void GAME_EXPORT Platform_SetMousePos( int x, int y ) +{ + // stub +} + +int Platform_JoyInit( int numjoy ) +{ + // stub + return 0; +} + +/* +======================== +Android_ShowMouse +======================== +*/ +void Android_ShowMouse( qboolean show ) +{ + if( m_ignore->value ) + show = true; + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.showMouse, show ); +} + +/* +======================== +Android_ShellExecute +======================== +*/ +void Platform_ShellExecute( const char *path, const char *parms ) +{ + jstring jstr; + + if( !path ) + return; // useless + + // get java.lang.String + jstr = (*jni.env)->NewStringUTF( jni.env, path ); + + // open browser + (*jni.env)->CallStaticVoidMethod(jni.env, jni.actcls, jni.shellExecute, jstr); + + // no need to free jstr +} + +int Platform_GetClipboardText( char *buffer, size_t size ) +{ + // stub + if( size ) buffer[0] = 0; + return 0; +} + +void Platform_SetClipboardText( const char *buffer ) +{ + // stub +} + +void Platform_SetCursorType( VGUI_DefaultCursor cursor ) +{ + if( cursor == dc_arrow ) + Android_ShowMouse( true ); + else + Android_ShowMouse( false ); + +} + +key_modifier_t Platform_GetKeyModifiers( void ) +{ + // stub + return KeyModifier_None; +} + +void Platform_PreCreateMove( void ) +{ + // stub +} + +/* +======================== +Android_RunEvents + +Execute all events from queue +======================== +*/ +void Platform_RunEvents( void ) +{ + int i; + + // enter events read + Android_Lock(); + pthread_mutex_unlock( &events.framemutex ); + + for( i = 0; i < events.count; i++ ) + { + switch( events.queue[i].type ) + { + case event_touch_down: + case event_touch_up: + case event_touch_move: + IN_TouchEvent( (touchEventType)events.queue[i].type, events.queue[i].arg, + events.queue[i].touch.x, events.queue[i].touch.y, + events.queue[i].touch.dx, events.queue[i].touch.dy ); + break; + + case event_key_down: + Key_Event( events.queue[i].arg, true ); + + if( events.queue[i].arg == K_AUX31 || events.queue[i].arg == K_AUX29 ) + { + host.force_draw_version = true; + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + } + break; + case event_key_up: + Key_Event( events.queue[i].arg, false ); + + if( events.queue[i].arg == K_AUX31 || events.queue[i].arg == K_AUX29 ) + { + host.force_draw_version = true; + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + } + break; + + case event_set_pause: + // destroy EGL surface when hiding application + if( !events.queue[i].arg ) + { + SNDDMA_Activate( true ); +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); + Android_UpdateSurface( true ); + host.status = HOST_FRAME; + SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval + host.force_draw_version = true; + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + } + + if( events.queue[i].arg ) + { + SNDDMA_Activate( false ); + Android_UpdateSurface( false ); + host.status = HOST_NOFOCUS; +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); + } + break; + + case event_resize: + // reinitialize EGL and change engine screen size + if( host.status == HOST_FRAME &&( refState.width != jni.width || refState.height != jni.height ) ) + { +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); + Android_UpdateSurface( true ); + SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval + VID_SetMode(); + } + break; + case event_joyadd: + Joy_AddEvent(); + break; + case event_joyremove: + Joy_RemoveEvent(); + break; + case event_joyball: + if( !Joy_IsActive() ) + Joy_AddEvent(); + Joy_BallMotionEvent( events.queue[i].ball.ball, + events.queue[i].ball.xrel, events.queue[i].ball.yrel ); + break; + case event_joyhat: + if( !Joy_IsActive() ) + Joy_AddEvent(); + Joy_HatMotionEvent( events.queue[i].hat.hat, events.queue[i].hat.key ); + break; + case event_joyaxis: + if( !Joy_IsActive() ) + Joy_AddEvent(); + Joy_AxisMotionEvent( events.queue[i].axis.axis, events.queue[i].axis.val ); + break; + case event_joybutton: + if( !Joy_IsActive() ) + Joy_AddEvent(); + Joy_ButtonEvent( events.queue[i].button.button, (byte)events.queue[i].button.down ); + break; + case event_ondestroy: + //host.skip_configs = true; // skip config save, because engine may be killed during config save + Sys_Quit(); + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.notify ); + break; + case event_onpause: +#ifdef PARANOID_CONFIG_SAVE + switch( host.status ) + { + case HOST_INIT: + case HOST_CRASHED: + case HOST_ERR_FATAL: + Con_Reportf( S_WARN "Abnormal host state during onPause (%d), skipping config save!\n", host.status ); + break; + default: + // restore all latched cheat cvars + Cvar_SetCheatState( true ); + Host_WriteConfig(); + } +#endif + // disable sound during call/screen-off + SNDDMA_Activate( false ); +// host.status = HOST_NOFOCUS; + // stop blocking UI thread + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.notify ); + + break; + case event_onresume: + // re-enable sound after onPause +// host.status = HOST_FRAME; + SNDDMA_Activate( true ); + host.force_draw_version = true; + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + break; + case event_onfocuschange: + host.force_draw_version = true; + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + break; + } + } + + events.count = 0; // no more events + + // text input handled separately to allow unicode symbols + for( i = 0; events.inputtext[i]; i++ ) + { + int ch; + + // if engine does not use utf-8, we need to convert it to preferred encoding + if( !Q_stricmp( cl_charset->string, "utf-8" ) ) + ch = (unsigned char)events.inputtext[i]; + else + ch = Con_UtfProcessCharForce( (unsigned char)events.inputtext[i] ); + + if( !ch ) // utf-8 + continue; + + // some keyboards may send enter as text + if( ch == '\n' ) + { + Key_Event( K_ENTER, true ); + Key_Event( K_ENTER, false ); + continue; + } + + // otherwise just push it by char, text render will decode unicode strings + CL_CharEvent( ch ); + } + events.inputtext[0] = 0; // no more text + + jnimouse.x += events.mousex; + events.mousex = 0; + jnimouse.y += events.mousey; + events.mousey = 0; + + //end events read + Android_Unlock(); + pthread_mutex_lock( &events.framemutex ); +} + +#endif // XASH_DEDICATED diff --git a/engine/platform/android/android_priv.h b/engine/platform/android/android_priv.h new file mode 100644 index 00000000..3af27197 --- /dev/null +++ b/engine/platform/android/android_priv.h @@ -0,0 +1,46 @@ +#pragma once +#ifndef ANDROID_PRIV_H +#define ANDROID_PRIV_H + +#include +#include +#include + +extern struct jnimethods_s +{ + jclass actcls; + JavaVM *vm; + JNIEnv *env; + jmethodID enableTextInput; + jmethodID vibrate; + jmethodID messageBox; + jmethodID notify; + jmethodID setTitle; + jmethodID setIcon; + jmethodID getAndroidId; + jmethodID saveID; + jmethodID loadID; + jmethodID showMouse; + jmethodID shellExecute; + jmethodID swapBuffers; + jmethodID toggleEGL; + jmethodID createGLContext; + jmethodID getGLAttribute; + jmethodID deleteGLContext; + jmethodID getSelectedPixelFormat; + jmethodID getSurface; + int width, height; +} jni; + + +extern struct jnimouse_s +{ + float x, y; +} jnimouse; + +// +// vid_android.c +// +void Android_UpdateSurface( qboolean active ); + +#endif // ANDROID_PRIV_H diff --git a/engine/platform/android/snd_opensles.c b/engine/platform/android/snd_opensles.c new file mode 100644 index 00000000..4cf5bbe0 --- /dev/null +++ b/engine/platform/android/snd_opensles.c @@ -0,0 +1,277 @@ +/* +Copyright (C) 2015 SiPlus, Chasseur de bots + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "common.h" +#include "platform/platform.h" +#if XASH_SOUND == SOUND_OPENSLES +#include +#include "pthread.h" +#include "sound.h" + +extern dma_t dma; + +static SLObjectItf snddma_android_engine = NULL; +static SLObjectItf snddma_android_outputMix = NULL; +static SLObjectItf snddma_android_player = NULL; +static SLBufferQueueItf snddma_android_bufferQueue; +static SLPlayItf snddma_android_play; + +static pthread_mutex_t snddma_android_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int snddma_android_size; + +static const SLInterfaceID *pSL_IID_ENGINE; +static const SLInterfaceID *pSL_IID_BUFFERQUEUE; +static const SLInterfaceID *pSL_IID_PLAY; +static SLresult SLAPIENTRY (*pslCreateEngine)( + SLObjectItf *pEngine, + SLuint32 numOptions, + const SLEngineOption *pEngineOptions, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean * pInterfaceRequired +); + +void SNDDMA_Activate( qboolean active ) +{ + if( !dma.initialized ) + return; + + if( active ) + { + memset( dma.buffer, 0, snddma_android_size * 2 ); + (*snddma_android_bufferQueue)->Enqueue( snddma_android_bufferQueue, dma.buffer, snddma_android_size ); + (*snddma_android_play)->SetPlayState( snddma_android_play, SL_PLAYSTATE_PLAYING ); + } + else + { + (*snddma_android_play)->SetPlayState( snddma_android_play, SL_PLAYSTATE_STOPPED ); + (*snddma_android_bufferQueue)->Clear( snddma_android_bufferQueue ); + } +} + +static void SNDDMA_Android_Callback( SLBufferQueueItf bq, void *context ) +{ + uint8_t *buffer2; + + pthread_mutex_lock( &snddma_android_mutex ); + + buffer2 = ( uint8_t * )dma.buffer + snddma_android_size; + (*bq)->Enqueue( bq, buffer2, snddma_android_size ); + memcpy( buffer2, dma.buffer, snddma_android_size ); + memset( dma.buffer, 0, snddma_android_size ); + dma.samplepos += dma.samples; + + pthread_mutex_unlock( &snddma_android_mutex ); +} + +static const char *SNDDMA_Android_Init( void ) +{ + SLresult result; + + SLEngineItf engine; + + int freq; + + SLDataLocator_BufferQueue sourceLocator; + SLDataFormat_PCM sourceFormat; + SLDataSource source; + + SLDataLocator_OutputMix sinkLocator; + SLDataSink sink; + + SLInterfaceID interfaceID; + SLboolean interfaceRequired; + + int samples; + void *handle = dlopen( "libOpenSLES.so", RTLD_LAZY ); + + if( !handle ) + return "dlopen for libOpenSLES.so"; + + pslCreateEngine = dlsym( handle, "slCreateEngine" ); + + if( !pslCreateEngine ) + return "resolve slCreateEngine"; + + pSL_IID_ENGINE = dlsym( handle, "SL_IID_ENGINE" ); + + if( !pSL_IID_ENGINE ) + return "resolve SL_IID_ENGINE"; + + pSL_IID_PLAY = dlsym( handle, "SL_IID_PLAY" ); + + if( !pSL_IID_PLAY ) + return "resolve SL_IID_PLAY"; + + pSL_IID_BUFFERQUEUE = dlsym( handle, "SL_IID_BUFFERQUEUE" ); + + if( !pSL_IID_BUFFERQUEUE ) + return "resolve SL_IID_BUFFERQUEUE"; + + + result = pslCreateEngine( &snddma_android_engine, 0, NULL, 0, NULL, NULL ); + if( result != SL_RESULT_SUCCESS ) return "slCreateEngine"; + result = (*snddma_android_engine)->Realize( snddma_android_engine, SL_BOOLEAN_FALSE ); + if( result != SL_RESULT_SUCCESS ) return "engine->Realize"; + result = (*snddma_android_engine)->GetInterface( snddma_android_engine, *pSL_IID_ENGINE, &engine ); + if( result != SL_RESULT_SUCCESS ) return "engine->GetInterface(ENGINE)"; + + result = (*engine)->CreateOutputMix( engine, &snddma_android_outputMix, 0, NULL, NULL ); + if( result != SL_RESULT_SUCCESS ) return "engine->CreateOutputMix"; + result = (*snddma_android_outputMix)->Realize( snddma_android_outputMix, SL_BOOLEAN_FALSE ); + if( result != SL_RESULT_SUCCESS ) return "outputMix->Realize"; + + freq = SOUND_DMA_SPEED; + sourceLocator.locatorType = SL_DATALOCATOR_BUFFERQUEUE; + sourceLocator.numBuffers = 2; + sourceFormat.formatType = SL_DATAFORMAT_PCM; + sourceFormat.numChannels = 2; // always stereo, because engine supports only stereo + sourceFormat.samplesPerSec = freq * 1000; + sourceFormat.bitsPerSample = 16; // always 16 bit audio + sourceFormat.containerSize = sourceFormat.bitsPerSample; + sourceFormat.channelMask = SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; + sourceFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; + source.pLocator = &sourceLocator; + source.pFormat = &sourceFormat; + + sinkLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; + sinkLocator.outputMix = snddma_android_outputMix; + sink.pLocator = &sinkLocator; + sink.pFormat = NULL; + + interfaceID = *pSL_IID_BUFFERQUEUE; + interfaceRequired = SL_BOOLEAN_TRUE; + + result = (*engine)->CreateAudioPlayer( engine, &snddma_android_player, &source, &sink, 1, &interfaceID, &interfaceRequired ); + if( result != SL_RESULT_SUCCESS ) return "engine->CreateAudioPlayer"; + result = (*snddma_android_player)->Realize( snddma_android_player, SL_BOOLEAN_FALSE ); + if( result != SL_RESULT_SUCCESS ) return "player->Realize"; + result = (*snddma_android_player)->GetInterface( snddma_android_player, *pSL_IID_BUFFERQUEUE, &snddma_android_bufferQueue ); + if( result != SL_RESULT_SUCCESS ) return "player->GetInterface(BUFFERQUEUE)"; + result = (*snddma_android_player)->GetInterface( snddma_android_player, *pSL_IID_PLAY, &snddma_android_play ); + if( result != SL_RESULT_SUCCESS ) return "player->GetInterface(PLAY)"; + result = (*snddma_android_bufferQueue)->RegisterCallback( snddma_android_bufferQueue, SNDDMA_Android_Callback, NULL ); + if( result != SL_RESULT_SUCCESS ) return "bufferQueue->RegisterCallback"; + + samples = s_samplecount.value; + if( !samples ) + samples = 4096; + + dma.format.channels = sourceFormat.numChannels; + dma.samples = samples * sourceFormat.numChannels; + dma.format.speed = freq; + snddma_android_size = dma.samples * ( sourceFormat.bitsPerSample >> 3 ); + dma.buffer = Z_Malloc( snddma_android_size * 2 ); + dma.samplepos = 0; + // dma.sampleframes = dma.samples / dma.format.channels; + dma.format.width = 2; + if( !dma.buffer ) return "malloc"; + + //snddma_android_mutex = trap_Mutex_Create(); + + dma.initialized = true; + + SNDDMA_Activate( true ); + + return NULL; +} + +qboolean SNDDMA_Init( void ) +{ + const char *initError; + + Msg( "OpenSL ES audio device initializing...\n" ); + + initError = SNDDMA_Android_Init(); + if( initError ) + { + Msg( S_ERROR "SNDDMA_Init: %s failed.\n", initError ); + SNDDMA_Shutdown(); + return false; + } + + Msg( "OpenSL ES audio initialized.\n" ); + dma.backendName = "OpenSL ES"; + return true; +} + +void SNDDMA_Shutdown( void ) +{ + Msg( "Closing OpenSL ES audio device...\n" ); + + if( snddma_android_player ) + { + (*snddma_android_player)->Destroy( snddma_android_player ); + snddma_android_player = NULL; + } + if( snddma_android_outputMix ) + { + (*snddma_android_outputMix)->Destroy( snddma_android_outputMix ); + snddma_android_outputMix = NULL; + } + if( snddma_android_engine ) + { + (*snddma_android_engine)->Destroy( snddma_android_engine ); + snddma_android_engine = NULL; + } + + if( dma.buffer ) + { + Z_Free( dma.buffer ); + dma.buffer = NULL; + } + + //if( snddma_android_mutex ) + //trap_Mutex_Destroy( &snddma_android_mutex ); + + Msg( "OpenSL ES audio device shut down.\n" ); +} + +void SNDDMA_Submit( void ) +{ + pthread_mutex_unlock( &snddma_android_mutex ); +} + +void SNDDMA_BeginPainting( void ) +{ + pthread_mutex_lock( &snddma_android_mutex ); +} + +qboolean VoiceCapture_Init( void ) +{ + return false; +} + +qboolean VoiceCapture_Activate( qboolean activate ) +{ + return false; +} + +qboolean VoiceCapture_Lock( qboolean lock ) +{ + return false; +} + +void VoiceCapture_Shutdown( void ) +{ + +} +#endif diff --git a/engine/platform/android/vid_android.c b/engine/platform/android/vid_android.c new file mode 100644 index 00000000..8519e024 --- /dev/null +++ b/engine/platform/android/vid_android.c @@ -0,0 +1,667 @@ +#include "platform/platform.h" +#in !XASH_SDL +#include "input.h" +#include "client.h" +#include "filesystem.h" +#include "platform/android/android_priv.h" +#include "vid_common.h" +#include +#include +#include +#include + +static struct vid_android_s +{ + int gl_attribs[REF_GL_ATTRIBUTES_COUNT]; + qboolean gl_attribs_set[REF_GL_ATTRIBUTES_COUNT]; + EGLint gl_api; + qboolean gles1; + void *libgles1, *libgles2; + qboolean has_context; + ANativeWindow* window; +} vid_android; + +static struct nw_s +{ + void (*release)(ANativeWindow* window); + int32_t (*getWidth)(ANativeWindow* window); + int32_t (*getHeight)(ANativeWindow* window); + int32_t (*getFormat)(ANativeWindow* window); + int32_t (*setBuffersGeometry)(ANativeWindow* window, int32_t width, int32_t height, int32_t format); + int32_t (*lock)(ANativeWindow* window, ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds); + int32_t (*unlockAndPost)(ANativeWindow* window); + ANativeWindow* (*fromSurface)(JNIEnv* env, jobject surface); +} nw; + +#define NW_FF(x) {"ANativeWindow_"#x, (void*)&nw.x} + + +static dllfunc_t android_funcs[] = +{ + NW_FF(release), + NW_FF(getWidth), + NW_FF(getHeight), + NW_FF(getFormat), + NW_FF(setBuffersGeometry), + NW_FF(lock), + NW_FF(unlockAndPost), + NW_FF(fromSurface), + { NULL, NULL } +}; +#undef NW_FF +dll_info_t android_info = { "libandroid.so", android_funcs, false }; + +static struct egl_s +{ + EGLSurface (*GetCurrentSurface)(EGLint readdraw); + EGLDisplay (*GetCurrentDisplay)(void); + EGLint (*GetError)(void); + EGLBoolean (*SwapBuffers)(EGLDisplay dpy, EGLSurface surface); + EGLBoolean (*SwapInterval)(EGLDisplay dpy, EGLint interval); + void *(*GetProcAddress)(const char *procname); +} egl; +#undef GetProcAddress +#define EGL_FF(x) {"egl"#x, (void*)&egl.x} +static dllfunc_t egl_funcs[] = +{ + EGL_FF(SwapInterval), + EGL_FF(SwapBuffers), + EGL_FF(GetError), + EGL_FF(GetCurrentDisplay), + EGL_FF(GetCurrentSurface), + EGL_FF(GetProcAddress), + { NULL, NULL } +}; +#undef EGL_FF +dll_info_t egl_info = { "libEGL.so", egl_funcs, false }; + +static struct nativeegl_s +{ + qboolean valid; + void *window; + EGLDisplay dpy; + EGLSurface surface; + EGLContext context; + EGLConfig cfg; + EGLint numCfg; + + const char *extensions; +} negl; + +/* +======================== +Android_SwapInterval +======================== +*/ +static void Android_SwapInterval( int interval ) +{ + if( negl.valid ) + egl.SwapInterval( negl.dpy, interval ); +} + +/* +======================== +Android_SetTitle +======================== +*/ +static void Android_SetTitle( const char *title ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setTitle, (*jni.env)->NewStringUTF( jni.env, title ) ); +} + +/* +======================== +Android_SetIcon +======================== +*/ +static void Android_SetIcon( const char *path ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setIcon, (*jni.env)->NewStringUTF( jni.env, path ) ); +} + +/* +======================== +Android_GetScreenRes + +Resolution got from last resize event +======================== +*/ +static void Android_GetScreenRes( int *width, int *height ) +{ + *width=jni.width, *height=jni.height; +} + +/* +======================== +Android_SwapBuffers + +Update screen. Use native EGL if possible +======================== +*/ +void GL_SwapBuffers( void ) +{ + if( negl.valid ) + { + egl.SwapBuffers( negl.dpy, negl.surface ); + } + else + { + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.swapBuffers ); + } +} + +/* +======================== +Android_UpdateSurface + +Check if we may use native EGL without jni calls +======================== +*/ +void Android_UpdateSurface( qboolean active ) +{ + negl.valid = false; + + if( nw.release ) + { + if( vid_android.window && !active ) + { + nw.release( vid_android.window ); + vid_android.window = NULL; + } + + if( active ) + { + jobject surf; + if( vid_android.window ) + nw.release( vid_android.window ); + surf = (*jni.env)->CallStaticObjectMethod(jni.env, jni.actcls, jni.getSurface); + Con_Printf("s %p\n", surf); + vid_android.window = nw.fromSurface(jni.env, surf); + Con_Printf("w %p\n", vid_android.window); + nw.setBuffersGeometry(vid_android.window, 0, 0, WINDOW_FORMAT_RGB_565 ); + (*jni.env)->DeleteLocalRef( jni.env, surf ); + } + return; + } + + if( !vid_android.has_context ) + return; + + if( ( active && host.status == HOST_FRAME ) || !active ) + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); + + if( active ) + (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); + + if( !Sys_CheckParm("-nativeegl") || !active ) + return; // enabled by user + + if( !egl.GetCurrentDisplay ) + return; + + negl.dpy = egl.GetCurrentDisplay(); + + if( negl.dpy == EGL_NO_DISPLAY ) + return; + + negl.surface = egl.GetCurrentSurface(EGL_DRAW); + + if( negl.surface == EGL_NO_SURFACE ) + return; + + // now check if swapBuffers does not give error + if( egl.SwapBuffers( negl.dpy, negl.surface ) == EGL_FALSE ) + return; + + // double check + if( egl.GetError() != EGL_SUCCESS ) + return; + + __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "native EGL enabled" ); + + negl.valid = true; +} + +/* +======================== +Android_GetGLAttribute +======================== +*/ +static int Android_GetGLAttribute( int eglAttr ) +{ + int ret = (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getGLAttribute, eglAttr ); + // Con_Reportf( "Android_GetGLAttribute( %i ) => %i\n", eglAttr, ret ); + return ret; +} + +int Android_GetSelectedPixelFormat( void ) +{ + return (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getSelectedPixelFormat ); +} + +qboolean R_Init_Video( const int type ) +{ + char buf[MAX_VA_STRING]; + qboolean retval; + + switch( Android_GetSelectedPixelFormat() ) + { + case 1: + refState.desktopBitsPixel = 16; + break; + case 2: + refState.desktopBitsPixel = 8; + break; + default: + refState.desktopBitsPixel = 32; + break; + } + + if( FS_FileExists( GI->iconpath, true ) ) + { + Q_snprintf( buf, sizeof( buf ), "%s/%s/%s", COM_CheckStringEmpty( host.rodir ) ? host.rodir : host.rootdir, GI->gamefolder, GI->iconpath ); + Android_SetIcon( buf ); + } + + Android_SetTitle( GI->title ); + + VID_StartupGamma(); + + switch( type ) + { + case REF_SOFTWARE: + glw_state.software = true; + break; + case REF_GL: + glw_state.software = false; + Sys_LoadLibrary( &egl_info ); + + if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", buf ) ) + glw_state.safe = bound( SAFE_NO, Q_atoi( buf ), SAFE_DONTCARE ); + + break; + default: + Host_Error( "Can't initialize unknown context type %d!\n", type ); + break; + } + + if( glw_state.software ) + { + uint arg; +// Con_Reportf( S_ERROR "Native software mode isn't supported on Android yet! :(\n" ); +// return false; + Sys_LoadLibrary( &android_info ); + Android_UpdateSurface( true ); + if( !SW_CreateBuffer( jni.width, jni.height, &arg, &arg, &arg, &arg, &arg ) ) + return false; + } + + while( !(retval = VID_SetMode()) ) + { + glw_state.safe++; + if( glw_state.safe > SAFE_LAST ) + return false; + } + + switch( type ) + { + case REF_GL: + // refdll also can check extensions + ref.dllFuncs.GL_InitExtensions(); + break; + case REF_SOFTWARE: + default: + break; + } + + host.renderinfo_changed = false; + + return true; +} + +void R_Free_Video( void ) +{ + // (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.deleteGLContext ); + + // VID_DestroyWindow (); + + // R_FreeVideoModes(); + Sys_FreeLibrary( &android_info ); + Sys_FreeLibrary( &egl_info ); + vid_android.has_context = false; + ref.dllFuncs.GL_ClearExtensions(); +} + +#define COPY_ATTR_IF_SET( refattr, attr ) \ + if( vid_android.gl_attribs_set[refattr] ) \ + { \ + attribs[i++] = attr; \ + attribs[i++] = vid_android.gl_attribs[refattr]; \ + } + +static size_t VID_GenerateConfig( EGLint *attribs, size_t size ) +{ + size_t i = 0; + + memset( attribs, 0, size * sizeof( EGLint ) ); + vid_android.gles1 = false; + memset( vid_android.gl_attribs, 0, sizeof( vid_android.gl_attribs )); + memset( vid_android.gl_attribs_set, 0, sizeof( vid_android.gl_attribs_set )); + + // refdll can request some attributes + ref.dllFuncs.GL_SetupAttributes( glw_state.safe ); + + COPY_ATTR_IF_SET( REF_GL_RED_SIZE, EGL_RED_SIZE ); + COPY_ATTR_IF_SET( REF_GL_GREEN_SIZE, EGL_GREEN_SIZE ); + COPY_ATTR_IF_SET( REF_GL_BLUE_SIZE, EGL_BLUE_SIZE ); + COPY_ATTR_IF_SET( REF_GL_ALPHA_SIZE, EGL_ALPHA_SIZE ); + COPY_ATTR_IF_SET( REF_GL_DEPTH_SIZE, EGL_DEPTH_SIZE ); + COPY_ATTR_IF_SET( REF_GL_STENCIL_SIZE, EGL_STENCIL_SIZE ); + COPY_ATTR_IF_SET( REF_GL_MULTISAMPLEBUFFERS, EGL_SAMPLE_BUFFERS ); + COPY_ATTR_IF_SET( REF_GL_MULTISAMPLESAMPLES, EGL_SAMPLES ); + + if( vid_android.gl_attribs_set[REF_GL_ACCELERATED_VISUAL] ) + { + attribs[i++] = EGL_CONFIG_CAVEAT; + attribs[i++] = vid_android.gl_attribs[REF_GL_ACCELERATED_VISUAL] ? EGL_NONE : EGL_DONT_CARE; + } + + // BigGL support + attribs[i++] = EGL_RENDERABLE_TYPE; + vid_android.gl_api = EGL_OPENGL_ES_API; + + if( vid_android.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] && + !( vid_android.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK] & REF_GL_CONTEXT_PROFILE_ES )) + { + attribs[i++] = EGL_OPENGL_BIT; + vid_android.gl_api = EGL_OPENGL_API; + } + else if( vid_android.gl_attribs_set[REF_GL_CONTEXT_MAJOR_VERSION] && + vid_android.gl_attribs[REF_GL_CONTEXT_MAJOR_VERSION] >= 2 ) + { + attribs[i++] = EGL_OPENGL_ES2_BIT; + } + else + { + i--; // erase EGL_RENDERABLE_TYPE + vid_android.gles1 = true; + } + + attribs[i++] = EGL_NONE; + + return i; +} + +static size_t VID_GenerateContextConfig( EGLint *attribs, size_t size ) +{ + size_t i = 0; + + memset( attribs, 0, size * sizeof( EGLint )); + + /*if( Q_strcmp( negl.extensions, " EGL_KHR_create_context ") ) + { + if( vid_android.gl_attribs_set[REF_GL_CONTEXT_FLAGS] ) + { + attribs[i++] = 0x30FC; // EGL_CONTEXT_FLAGS_KHR + attribs[i++] = vid_android.gl_attribs[REF_GL_CONTEXT_FLAGS] & ((REF_GL_CONTEXT_ROBUST_ACCESS_FLAG << 1) - 1); + } + + if( vid_android.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] ) + { + int val = vid_android.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK]; + + if( val & ( (REF_GL_CONTEXT_PROFILE_COMPATIBILITY << 1) - 1 ) ) + { + attribs[i++] = 0x30FD; // EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; + attribs[i++] = val; + } + } + + COPY_ATTR_IF_SET( REF_GL_CONTEXT_MAJOR_VERSION, EGL_CONTEXT_CLIENT_VERSION ); + COPY_ATTR_IF_SET( REF_GL_CONTEXT_MINOR_VERSION, 0x30FB ); + } + else*/ + { + // without extension we can set only major version + COPY_ATTR_IF_SET( REF_GL_CONTEXT_MAJOR_VERSION, EGL_CONTEXT_CLIENT_VERSION ); + } + + attribs[i++] = EGL_NONE; + + return i; +} + +qboolean VID_SetMode( void ) +{ + EGLint format; + jintArray attribs, contextAttribs; + static EGLint nAttribs[32+1], nContextAttribs[32+1]; + const size_t attribsSize = ARRAYSIZE( nAttribs ); + size_t s1, s2; + + if( vid_android.has_context ) + { + R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway + return true; + } + + s1 = VID_GenerateConfig(nAttribs, attribsSize); + s2 = VID_GenerateContextConfig(nContextAttribs, attribsSize); + + attribs = (*jni.env)->NewIntArray( jni.env, s1 ); + contextAttribs = (*jni.env)->NewIntArray( jni.env, s2 ); + + (*jni.env)->SetIntArrayRegion( jni.env, attribs, 0, s1, nAttribs ); + (*jni.env)->SetIntArrayRegion( jni.env, contextAttribs, 0, s2, nContextAttribs ); + + R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway + + if( glw_state.software ) + return true; + + if( (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.createGLContext, attribs, contextAttribs ) ) + { + vid_android.has_context = true; + return true; + } + + return false; +} + +rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) +{ + int render_w, render_h; + uint rotate = vid_rotate->value; + + Android_GetScreenRes(&width, &height); + + render_w = width; + render_h = height; + + Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height); + + if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) ) + { + if( rotate & 1 ) + { + int swap = render_w; + + render_w = render_h; + render_h = swap; + } + + render_h /= vid_scale->value; + render_w /= vid_scale->value; + } + else + { + Con_Printf( S_WARN "failed to setup screen transform\n" ); + } + + R_SaveVideoMode( width, height, render_w, render_h ); + + refState.wideScreen = true; // V_AdjustFov will check for widescreen + + return rserr_ok; +} + +int GL_SetAttribute( int attr, int val ) +{ + if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT ) + return -1; + + vid_android.gl_attribs[attr] = val; + vid_android.gl_attribs_set[attr] = true; + return 0; +} + +int GL_GetAttribute( int attr, int *val ) +{ + EGLBoolean ret; + + if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT ) + return -1; + + if( !val ) + return -1; + + switch( attr ) + { + case REF_GL_RED_SIZE: + *val = Android_GetGLAttribute( EGL_RED_SIZE ); + return 0; + case REF_GL_GREEN_SIZE: + *val = Android_GetGLAttribute( EGL_GREEN_SIZE ); + return 0; + case REF_GL_BLUE_SIZE: + *val = Android_GetGLAttribute( EGL_BLUE_SIZE ); + return 0; + case REF_GL_ALPHA_SIZE: + *val = Android_GetGLAttribute( EGL_ALPHA_SIZE ); + return 0; + case REF_GL_DEPTH_SIZE: + *val = Android_GetGLAttribute( EGL_DEPTH_SIZE ); + return 0; + case REF_GL_STENCIL_SIZE: + *val = Android_GetGLAttribute( EGL_STENCIL_SIZE ); + return 0; + case REF_GL_MULTISAMPLESAMPLES: + *val = Android_GetGLAttribute( EGL_SAMPLES ); + return 0; + } + + return -1; +} + +int R_MaxVideoModes( void ) +{ + return 0; +} + +vidmode_t* R_GetVideoMode( int num ) +{ + return NULL; +} + +void* GL_GetProcAddress( const char *name ) // RenderAPI requirement +{ + void *gles; + void *addr; + + if( vid_android.gles1 ) + { + if( !vid_android.libgles1 ) + vid_android.libgles1 = dlopen("libGLESv1_CM.so", RTLD_NOW); + gles = vid_android.libgles1; + } + else + { + if( !vid_android.libgles2 ) + vid_android.libgles2 = dlopen("libGLESv2.so", RTLD_NOW); + gles = vid_android.libgles2; + } + + if( gles && ( addr = dlsym(gles, name ) ) ) + return addr; + + if( !egl.GetProcAddress ) + return NULL; + + return egl.GetProcAddress( name ); +} + +void GL_UpdateSwapInterval( void ) +{ + // disable VSync while level is loading + if( cls.state < ca_active ) + { + Android_SwapInterval( 0 ); + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + } + else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) + { + ClearBits( gl_vsync->flags, FCVAR_CHANGED ); + Android_SwapInterval( gl_vsync->value ); + } +} + +void *SW_LockBuffer( void ) +{ + ANativeWindow_Buffer buffer; + if( !nw.lock || !vid_android.window ) + return NULL; + if( nw.lock( vid_android.window, &buffer, NULL ) ) + return NULL; + if( buffer.width < refState.width || buffer.height < refState.height ) + return NULL; + return buffer.bits; +} + +void SW_UnlockBuffer( void ) +{ + if( nw.unlockAndPost ) + nw.unlockAndPost( vid_android.window ); +} + +qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) +{ + ANativeWindow_Buffer buffer; + int lock; + if( !nw.lock || !vid_android.window ) + return false; + nw.unlockAndPost( vid_android.window ); + + if( ( lock = nw.lock( vid_android.window, &buffer, NULL ) ) ) + { + Con_Printf( "SW_CreateBuffer: lock %d\n", lock ); + return false; + } + nw.unlockAndPost( vid_android.window ); + Con_Printf( "SW_CreateBuffer: buffer %d %d %x %d %p\n", buffer.width, buffer.height, buffer.format, buffer.stride, buffer.bits ); + if( width > buffer.width || height > buffer.height ) + { + Con_Printf( "SW_CreateBuffer: buffer too small %d %d\n", width, height ); + // resize event missed? + if( jni.width < buffer.width ) + jni.width = buffer.width; + if( jni.height < buffer.height ) + jni.width = buffer.height; + VID_SetMode(); + Android_UpdateSurface( 1 ); + return false; + } + if( buffer.format != WINDOW_FORMAT_RGB_565 ) + { + Con_Printf( "SW_CreateBuffer: wrong format %d\n", buffer.format ); + return false; + } + Con_Printf( "SW_CreateBuffer: ok\n" ); + *stride = buffer.stride; + + *bpp = 2; + *r = (((1 << 5) - 1) << (5+6)); + *g = (((1 << 6) - 1) << (5)); + *b = (((1 << 5) - 1) << (0)); + return true; +} +#endif \ No newline at end of file