diff --git a/common/backends.h b/common/backends.h index 6fd51f19f6..3040eb196c 100644 --- a/common/backends.h +++ b/common/backends.h @@ -18,17 +18,20 @@ 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,6 +44,7 @@ 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 diff --git a/common/defaults.h b/common/defaults.h index cbcbd13e88..c3cb22ef9c 100644 --- a/common/defaults.h +++ b/common/defaults.h @@ -52,6 +52,25 @@ 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 #elif XASH_LINUX // we are building for Linux without SDL2, can draw only to framebuffer yet #ifndef XASH_VIDEO @@ -151,8 +170,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 7bba5ca582..de63238211 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 0000000000..ef4fa9e798 --- /dev/null +++ b/engine/platform/android/android_nosdl.c @@ -0,0 +1,1064 @@ +/* +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 +#include + +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#define PR_SET_PTRACER_ANY ((unsigned long)-1) +#endif + +#ifndef JNICALL +#define JNICALL // a1ba: workaround for my IDE, where Java files are not included +#define JNIEXPORT +#endif + +static CVAR_DEFINE_AUTO( android_sleep, "1", FCVAR_ARCHIVE, "Enable sleep in background" ); + +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; + +static jmp_buf crash_frame, restore_frame; +static char crash_text[1024]; + +#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_XashBinding_##name( JNIEnv *env, jclass clazz VA_ARGS(__VA_ARGS__) ) + +static volatile int _debugger_present = -1; +static void _sigtrap_handler(int signum) +{ + _debugger_present = 0; + __android_log_print( ANDROID_LOG_ERROR, "XashGDBWait", "It's a TRAP!" ); +} + +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); + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + + /* Init callbacks. */ + + jni.env = env; + jni.bindcls = (*env)->FindClass(env, "su/xash/engine/XashBinding"); + jni.enableTextInput = (*env)->GetStaticMethodID(env, jni.bindcls, "showKeyboard", "(I)V"); + jni.vibrate = (*env)->GetStaticMethodID(env, jni.bindcls, "vibrate", "(I)V" ); + jni.messageBox = (*env)->GetStaticMethodID(env, jni.bindcls, "messageBox", "(Ljava/lang/String;Ljava/lang/String;)V"); + jni.notify = (*env)->GetStaticMethodID(env, jni.bindcls, "engineThreadNotify", "()V"); + jni.setTitle = (*env)->GetStaticMethodID(env, jni.bindcls, "setTitle", "(Ljava/lang/String;)V"); + jni.setIcon = (*env)->GetStaticMethodID(env, jni.bindcls, "setIcon", "(Ljava/lang/String;)V"); + jni.getAndroidId = (*env)->GetStaticMethodID(env, jni.bindcls, "getAndroidID", "()Ljava/lang/String;"); + jni.saveID = (*env)->GetStaticMethodID(env, jni.bindcls, "saveID", "(Ljava/lang/String;)V"); + jni.loadID = (*env)->GetStaticMethodID(env, jni.bindcls, "loadID", "()Ljava/lang/String;"); + jni.showMouse = (*env)->GetStaticMethodID(env, jni.bindcls, "showMouse", "(I)V"); + jni.shellExecute = (*env)->GetStaticMethodID(env, jni.bindcls, "shellExecute", "(Ljava/lang/String;)V"); + + jni.swapBuffers = (*env)->GetStaticMethodID(env, jni.bindcls, "swapBuffers", "()V"); + jni.toggleEGL = (*env)->GetStaticMethodID(env, jni.bindcls, "toggleEGL", "(I)V"); + jni.createGLContext = (*env)->GetStaticMethodID(env, jni.bindcls, "createGLContext", "([I[I)Z"); + jni.getGLAttribute = (*env)->GetStaticMethodID(env, jni.bindcls, "getGLAttribute", "(I)I"); + jni.deleteGLContext = (*env)->GetStaticMethodID(env, jni.bindcls, "deleteGLContext", "()Z"); + jni.getSurface = (*env)->GetStaticMethodID(env, jni.bindcls, "getNativeSurface", "(I)Landroid/view/Surface;"); + jni.preShutdown = (*env)->GetStaticMethodID(env, jni.bindcls, "preShutdown", "()V"); + + // jni fails when called from signal handler callback, so jump here when called messagebox from handler + if( setjmp( crash_frame )) + { + // note: this will destroy stack and shutting down engine with return-from-main + // will be impossible, but Sys_Quit works + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.messageBox, (*jni.env)->NewStringUTF( jni.env, "crash" ), (*jni.env)->NewStringUTF( jni.env , crash_text ) ); + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.preShutdown ); + + // UB, but who cares, we already crashed! + longjmp( restore_frame, 1 ); + return 127; // unreach + } + + if( getenv( "XASH3D_GDB_WAIT" )) + { + signal(SIGTRAP, _sigtrap_handler); + while(_debugger_present <= 0) + { + _debugger_present = 1; + INLINE_RAISE( SIGTRAP ); + INLINE_NANOSLEEP1(); + } + signal(SIGTRAP, SIG_DFL); + } + + /* 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.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.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 >= ARRAYSIZE( s_android_scantokey )) + { + 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 ) +{ + prctl(PR_SET_DUMPABLE, 1); + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + return JNI_VERSION_1_6; +} + +/* +======================== +Platform_Init + +Initialize android-related cvars +======================== +*/ +void Android_Init( void ) +{ + Cvar_RegisterVariable( &android_sleep ); +} + +void Android_Shutdown( void ) +{ + if( host.crashed ) + return; + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.preShutdown ); +} + +/* +======================== +Android_EnableTextInput + +Show virtual keyboard +======================== +*/ +void Platform_EnableTextInput( qboolean enable ) +{ + if( host.crashed ) + return; + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.enableTextInput, enable ); +} + +/* +======================== +Android_Vibrate +======================== +*/ +void Platform_Vibrate( float life, char flags ) +{ + if( life ) + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.vibrate, (int)life ); +} + +/* +======================== +Android_GetNativeObject +======================== +*/ +void *Android_GetNativeObject( const char *objName ) +{ + void *object = NULL; + + if( !strcasecmp( objName, "JNIEnv" ) ) + { + object = (void*)jni.env; + } + else if( !strcasecmp( objName, "ActivityClass" ) ) + { + if( !jni.actcls ) + jni.actcls = (*jni.env)->FindClass( jni.env, "su/xash/engine/XashActivity" ); + 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 calls from signal handler broken since some android version, so move to "safe" frame and pass text + if( host.crashed ) + { + if( setjmp( restore_frame ) ) + return; + Q_strncpy( crash_text, text, 1024 ); + longjmp( crash_frame, 1 ); + return; + } + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, 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.bindcls, 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.bindcls, 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.bindcls, 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.bindcls, 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.bindcls, 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_ProcessEvents + +Execute all events from queue +======================== +*/ +static void Android_ProcessEvents( 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_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_time = host.realtime + FORCE_DRAW_VERSION_TIME; + } + break; + + case event_set_pause: + // destroy EGL surface when hiding application + Con_Printf( "pause event %d\n", events.queue[i].arg ); + if( !events.queue[i].arg ) + { + SNDDMA_Activate( true ); +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.toggleEGL, 1 ); + Android_UpdateSurface( surface_active ); + SetBits( gl_vsync.flags, FCVAR_CHANGED ); // set swap interval + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + } + + if( events.queue[i].arg ) + { + SNDDMA_Activate( false ); + Android_UpdateSurface( !android_sleep.value ? surface_dummy : surface_pause ); +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.toggleEGL, 0 ); + } + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.notify ); + break; + + case event_resize: + // reinitialize EGL and change engine screen size + if( ( host.status == HOST_FRAME || host.status == HOST_NOFOCUS ) &&( refState.width != jni.width || refState.height != jni.height )) + { +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.toggleEGL, 0 ); +// (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.toggleEGL, 1 ); + Con_DPrintf("resize event\n"); + Android_UpdateSurface( surface_pause ); + Android_UpdateSurface( surface_active ); + SetBits( gl_vsync.flags, FCVAR_CHANGED ); // set swap interval + VID_SetMode(); + } + else + { + Con_DPrintf("resize skip %d %d %d %d %d\n", jni.width, jni.height, refState.width, refState.height, host.status ); + } + 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.bindcls, 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_SLEEP; + // stop blocking UI thread + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.notify ); + + break; + case event_onresume: + // re-enable sound after onPause + //host.status = HOST_FRAME; + SNDDMA_Activate( true ); + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + break; + case event_onfocuschange: + 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 ); +} + +void Platform_RunEvents( void ) +{ + Android_ProcessEvents(); + + // do not allow running frames while android_sleep is 1, but surface/context not restored + while( android_sleep.value && host.status == HOST_SLEEP ) + { + usleep( 20000 ); + Android_ProcessEvents(); + } +} + +#endif // XASH_DEDICATED diff --git a/engine/platform/android/android_priv.h b/engine/platform/android/android_priv.h new file mode 100644 index 0000000000..bad06f81ed --- /dev/null +++ b/engine/platform/android/android_priv.h @@ -0,0 +1,55 @@ +#pragma once +#ifndef ANDROID_PRIV_H +#define ANDROID_PRIV_H + +#include +#include +#include + +extern struct jnimethods_s +{ + jclass actcls; + jclass bindcls; + 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 getSurface; + jmethodID preShutdown; + int width, height; +} jni; + +typedef enum surfacestate_e +{ + surface_pause, + surface_active, + surface_dummy, + +} surfacestate_t; + + +extern struct jnimouse_s +{ + float x, y; +} jnimouse; + +// +// vid_android.c +// +void Android_UpdateSurface( surfacestate_t state ); + +#endif // ANDROID_PRIV_H diff --git a/engine/platform/android/dlsym-weak.cpp b/engine/platform/android/dlsym-weak.cpp index d585af7d1c..8f78bf647b 100644 --- a/engine/platform/android/dlsym-weak.cpp +++ b/engine/platform/android/dlsym-weak.cpp @@ -25,30 +25,36 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ - -#if defined __ANDROID__ && !defined XASH_64BIT +#include "build.h" +#if !XASH_64BIT #include #include +#include #include "linker.h" +extern "C" { +#include "lib_android.h" +} -static Elf_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) { +static Elf_Sym* soinfo_elf_lookup( soinfo* si, unsigned hash, const char* name ) +{ Elf_Sym* symtab = si->symtab; const char* strtab = si->strtab; if( si->nbucket == 0 ) return NULL; - for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]) { + for( unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n] ) + { Elf_Sym* s = symtab + n; - if (strcmp(strtab + s->st_name, name)) continue; + if( strcmp( strtab + s->st_name, name )) continue; /* only concern ourselves with global and weak symbol definitions */ - switch (ELF_ST_BIND(s->st_info)) { + switch( ELF_ST_BIND( s->st_info )) + { case STB_GLOBAL: case STB_WEAK: - if (s->st_shndx == SHN_UNDEF) { + if( s->st_shndx == SHN_UNDEF ) continue; - } return s; } } @@ -56,11 +62,44 @@ static Elf_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) { return NULL; } -static unsigned elfhash(const char* _name) { - const unsigned char* name = (const unsigned char*) _name; +static Elf_Sym* soinfo_elf_lookup_reverse(soinfo* si, size_t addr) +{ + Elf_Sym* symtab = si->symtab; + + if( si->nbucket == 0 ) + return NULL; + + for( int j = 0; j < si->nbucket; j++ ) + { + for( unsigned n = si->bucket[j]; n != 0; n = si->chain[n] ) + { + Elf_Sym* s = symtab + n; + if( s->st_value != addr )continue; + + /* only concern ourselves with global and weak symbol definitions */ + switch( ELF_ST_BIND( s->st_info )) + { + case STB_GLOBAL: + case STB_LOCAL: + case STB_WEAK: + if (s->st_shndx == SHN_UNDEF) + continue; + return s; + } + } + } + + return NULL; +} + + +static unsigned elfhash( const char* _name ) +{ + const unsigned char* name = ( const unsigned char* ) _name; unsigned h = 0, g; - while(*name) { + while(*name) + { h = (h << 4) + *name++; g = h & 0xf0000000; h ^= g; @@ -78,21 +117,73 @@ static unsigned elfhash(const char* _name) { Binary Interface) where in Chapter 5 it discuss resolving "Shared Object Dependencies" in breadth first search order. */ -static Elf_Sym* dlsym_handle_lookup(soinfo* si, const char* name) { - return soinfo_elf_lookup(si, elfhash(name), name); +static Elf_Sym* dlsym_handle_lookup( soinfo* si, const char* name ) +{ + return soinfo_elf_lookup( si, elfhash( name ), name ); +} + +static int dladdr_fallback( const void *addr, Dl_info *info ) +{ + static soinfo *server_info; + Elf_Sym *sym; + + if( !server_info ) + server_info = ( soinfo* )ANDROID_GetServerLibrary(); + if( !server_info ) + return 0; + //__android_log_print( ANDROID_LOG_ERROR, "dladdr_fb", "%p %p\n", addr, server_info ); + + sym = soinfo_elf_lookup_reverse( server_info, ((char*)addr) - ((char*)server_info->base )); + //__android_log_print( ANDROID_LOG_ERROR, "dladdr_fb", "sym %p %p\n", addr, sym ); + if( sym ) + { + info->dli_sname = server_info->strtab + sym->st_name; + info->dli_fname = "server"; + info->dli_fbase = (void*)server_info->base; + info->dli_saddr = (void*)addr; + //__android_log_print( ANDROID_LOG_ERROR, "dladdr_fb", "name %p %s\n", addr, info->dli_sname ); + return 1; + } + + return 0; +} + + + +extern "C" int __attribute__((visibility("hidden"))) dladdr( const void *addr, Dl_info *info ) +{ + typedef int (*PFNDLADDR)( const void *addr, Dl_info *info ); + PFNDLADDR pfn_dladdr; + + if( !pfn_dladdr ) + { + /* android does not have libdl, but have soinfo record for it from linker + * use dlopen to get this record directly */ + void *lib = dlopen( "libdl.so", RTLD_NOW ); + + if( lib ) + pfn_dladdr = (PFNDLADDR)dlsym( lib, "dladdr" ); + if( pfn_dladdr == (PFNDLADDR)dladdr ) + pfn_dladdr = 0; + if( !pfn_dladdr ) + pfn_dladdr = (PFNDLADDR)dladdr_fallback; + } + + return pfn_dladdr( addr, info ); } -extern "C" void* dlsym_weak(void* handle, const char* symbol) { + +extern "C" void* dlsym_weak( void* handle, const char* symbol ) { soinfo* found = NULL; Elf_Sym* sym = NULL; - found = reinterpret_cast(handle); - sym = dlsym_handle_lookup(found, symbol); + found = reinterpret_cast( handle ); + sym = dlsym_handle_lookup( found, symbol ); - if (sym != NULL) { - return reinterpret_cast(sym->st_value + found->base/*load_bias*/); + if ( sym != NULL ) { + return reinterpret_cast( sym->st_value + found->base /*load_bias*/ ); } - __android_log_print(ANDROID_LOG_ERROR, "dlsym-weak", "Failed when looking up %s\n", symbol); + __android_log_print( ANDROID_LOG_ERROR, "dlsym-weak", "Failed when looking up %s\n", symbol ); return NULL; } #endif diff --git a/engine/platform/android/eglutil.c b/engine/platform/android/eglutil.c new file mode 100644 index 0000000000..2011adb7ad --- /dev/null +++ b/engine/platform/android/eglutil.c @@ -0,0 +1,453 @@ +/* +eglutil.c - EGL context utility +Copyright (C) 2023 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 "eglutil.h" +#include "client.h" +#include "vid_common.h" +#include +#include + +struct eglapi_s egl; + +#undef GetProcAddress // windows.h? +#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), + EGL_FF(GetConfigAttrib), + EGL_FF(GetDisplay), + EGL_FF(Initialize), + EGL_FF(Terminate), + EGL_FF(QueryString), + EGL_FF(ChooseConfig), + EGL_FF(CreateWindowSurface), + EGL_FF(CreateContext), + EGL_FF(DestroyContext), + EGL_FF(MakeCurrent), + EGL_FF(BindAPI), + EGL_FF(DestroySurface), + { NULL, NULL } +}; +#undef EGL_FF +static dll_info_t egl_info = { "libEGL.so", egl_funcs, false }; + +struct eglstate_s eglstate; + + + +/* +=================== + +refapi/egl wrappers + +=================== +*/ + +int EGL_SetAttribute( int attr, int val ) +{ + if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT ) + return -1; + + eglstate.gl_attribs[attr] = val; + eglstate.gl_attribs_set[attr] = true; + return 0; +} + +#define COPY_ATTR_IF_SET( refattr, attr ) \ + if( eglstate.gl_attribs_set[refattr] ) \ + { \ + attribs[i++] = attr; \ + attribs[i++] = eglstate.gl_attribs[refattr]; \ + } + +size_t EGL_GenerateConfig( EGLint *attribs, size_t size ) +{ + size_t i = 0; + + memset( attribs, 0, size * sizeof( EGLint ) ); + eglstate.gles1 = false; + memset( eglstate.gl_attribs, 0, sizeof( eglstate.gl_attribs )); + memset( eglstate.gl_attribs_set, 0, sizeof( eglstate.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( eglstate.gl_attribs_set[REF_GL_ACCELERATED_VISUAL] ) + { + attribs[i++] = EGL_CONFIG_CAVEAT; + attribs[i++] = eglstate.gl_attribs[REF_GL_ACCELERATED_VISUAL] ? EGL_NONE : EGL_DONT_CARE; + } + + // BigGL support + attribs[i++] = EGL_RENDERABLE_TYPE; + eglstate.gl_api = EGL_OPENGL_ES_API; + + if( eglstate.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] && + !( eglstate.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK] & REF_GL_CONTEXT_PROFILE_ES )) + { + attribs[i++] = EGL_OPENGL_BIT; + eglstate.gl_api = EGL_OPENGL_API; + } + else if( eglstate.gl_attribs_set[REF_GL_CONTEXT_MAJOR_VERSION] && + eglstate.gl_attribs[REF_GL_CONTEXT_MAJOR_VERSION] >= 2 ) + { + attribs[i++] = EGL_OPENGL_ES2_BIT; + } + else + { + i--; // erase EGL_RENDERABLE_TYPE + eglstate.gles1 = true; + } + + attribs[i++] = EGL_NONE; + + return i; +} + + +size_t EGL_GenerateContextConfig( EGLint *attribs, size_t size ) +{ + size_t i = 0; + + memset( attribs, 0, size * sizeof( EGLint )); + + if( Q_strstr( eglstate.extensions, "EGL_KHR_create_context") ) + { + Con_DPrintf( S_NOTE "EGL_KHR_create_context found, setting additional context flags\n"); + + if( eglstate.gl_attribs_set[REF_GL_CONTEXT_FLAGS] ) + { + attribs[i++] = 0x30FC; // EGL_CONTEXT_FLAGS_KHR + attribs[i++] = eglstate.gl_attribs[REF_GL_CONTEXT_FLAGS] & ((REF_GL_CONTEXT_ROBUST_ACCESS_FLAG << 1) - 1); + } + + if( eglstate.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] ) + { + int val = eglstate.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 ); + if( eglstate.gl_attribs_set[REF_GL_CONTEXT_FLAGS] && (eglstate.gl_attribs[REF_GL_CONTEXT_FLAGS] & REF_GL_CONTEXT_DEBUG_FLAG )) + { + attribs[i++] = 0x31B0; // EGL_CONTEXT_OPENGL_DEBUG; + attribs[i++] = EGL_TRUE; + } + } + + + attribs[i++] = EGL_NONE; + + return i; +} + + + +/* +========================= +EGL_GetAttribute + +query +========================= +*/ +int EGL_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: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_RED_SIZE, val ); + return 0; + case REF_GL_GREEN_SIZE: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_GREEN_SIZE, val ); + return 0; + case REF_GL_BLUE_SIZE: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_BLUE_SIZE, val ); + return 0; + case REF_GL_ALPHA_SIZE: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_ALPHA_SIZE, val ); + return 0; + case REF_GL_DEPTH_SIZE: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_DEPTH_SIZE, val ); + return 0; + case REF_GL_STENCIL_SIZE: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_STENCIL_SIZE, val ); + return 0; + case REF_GL_MULTISAMPLESAMPLES: + ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_SAMPLES, val ); + return 0; + } + + return -1; +} + + +/* +========================= +EGL_UpdateSurface + +destroy old surface, recreate and make context current +must be called with valid context +========================= +*/ +qboolean EGL_UpdateSurface( void *window, qboolean dummy ) +{ + if( !eglstate.valid ) + return false; + + egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ); + host.status = HOST_SLEEP; + + if( eglstate.surface ) + { + egl.DestroySurface( eglstate.dpy, eglstate.surface ); + eglstate.surface = EGL_NO_SURFACE; + } + + if( !window ) + { + + if( dummy && eglstate.support_surfaceless_context ) + { + if( egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglstate.context )) + { + Con_Reportf( S_NOTE "EGL_UpdateSurface: using surfaceless mode\n" ); + return true; + } + else + { + egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ); + eglstate.support_surfaceless_context = false; + } + Con_Reportf( S_NOTE "EGL_UpdateSurface: missing native window, detaching context\n" ); + } + + return false; // let platform fallback to dummy surface or pause engine + } + + if(( eglstate.surface = egl.CreateWindowSurface( eglstate.dpy, eglstate.cfg, window, NULL )) == EGL_NO_SURFACE ) + { + Con_Reportf( S_ERROR "eglCreateWindowSurface returned error: 0x%x\n", egl.GetError() ); + return false; + } + + if( !egl.MakeCurrent( eglstate.dpy, eglstate.surface, eglstate.surface, eglstate.context )) + { + Con_Reportf( S_ERROR "eglMakeCurrent returned error: 0x%x\n", egl.GetError() ); + return false; + } + + Con_DPrintf( S_NOTE "restored current context\n" ); + if( !dummy) + host.status = HOST_FRAME; + + return true; + +} + + +/* +========================= +EGL_CreateContext + +query attributes for ref and create context +========================= +*/ +qboolean EGL_CreateContext( void ) +{ + EGLint attribs[32+1], contextAttribs[32+1]; + const size_t attribsSize = ARRAYSIZE( attribs ); + size_t s1, s2; + + if( !eglstate.dpy && ( eglstate.dpy = egl.GetDisplay( EGL_DEFAULT_DISPLAY )) == EGL_NO_DISPLAY ) + { + Con_Reportf( S_ERROR "eglGetDisplay returned error: 0x%x\n", egl.GetError() ); + return false; + } + + eglstate.extensions = egl.QueryString( eglstate.dpy, EGL_EXTENSIONS ); + + s1 = EGL_GenerateConfig(attribs, attribsSize); + s2 = EGL_GenerateContextConfig(contextAttribs, attribsSize); + + + if( !egl.BindAPI( eglstate.gl_api )) + { + Con_Reportf( S_ERROR "eglBindAPI returned error: 0x%x\n", egl.GetError() ); + return false; + } + + if( !egl.Initialize( eglstate.dpy, NULL, NULL )) + { + Con_Reportf( S_ERROR "eglInitialize returned error: 0x%x\n", egl.GetError() ); + return false; + } + + if( !egl.ChooseConfig( eglstate.dpy, attribs, &eglstate.cfg, 1, &eglstate.numCfg )) + { + Con_Reportf( S_ERROR "eglChooseConfig returned error: 0x%x\n", egl.GetError() ); + return false; + } + + if(( eglstate.context = egl.CreateContext( eglstate.dpy, eglstate.cfg, NULL, contextAttribs )) == EGL_NO_CONTEXT ) + { + Con_Reportf( S_ERROR "eglCreateContext returned error: 0x%x\n", egl.GetError() ); + return false; + } + + eglstate.valid = true; + eglstate.imported = false; + + // now check if it's safe to use surfaceless context here without surface fallback + if( eglstate.extensions && Q_strstr( eglstate.extensions, "EGL_KHR_surfaceless_context" )) + eglstate.support_surfaceless_context = true; + + + return true; +} + +/* +=========================== +EGL_ImportContext + +import current egl context to use EGL functions +=========================== +*/ +qboolean EGL_ImportContext( void ) +{ + if( !egl.GetCurrentDisplay ) + return false; + + eglstate.dpy = egl.GetCurrentDisplay(); + + if( eglstate.dpy == EGL_NO_DISPLAY ) + return false; + + eglstate.surface = egl.GetCurrentSurface( EGL_DRAW ); + + if( eglstate.surface == EGL_NO_SURFACE ) + return false; + + // now check if swapBuffers does not give error + if( egl.SwapBuffers( eglstate.dpy, eglstate.surface ) == EGL_FALSE ) + return false; + + // double check + if( egl.GetError() != EGL_SUCCESS ) + return false; + + eglstate.extensions = egl.QueryString( eglstate.dpy, EGL_EXTENSIONS ); + + eglstate.valid = eglstate.imported = true; + return true; +} + +/* +========================= +EGL_Terminate +========================= +*/ +void EGL_Terminate( void ) +{ + egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ); + + egl.DestroyContext( eglstate.dpy, eglstate.context ); + + egl.DestroySurface( eglstate.dpy, eglstate.surface ); + + egl.Terminate( eglstate.dpy ); + + Sys_FreeLibrary( &egl_info ); +} + +/* +========================= +EGL_GetProcAddress + +eglGetProcAddress/dlsym wrapper +========================= +*/ +void *EGL_GetProcAddress( const char *name ) +{ + void *gles; + void *addr; + + // TODO: cross-platform loading + if( eglstate.gles1 ) + { + if( !eglstate.libgles1 ) + eglstate.libgles1 = dlopen( "libGLESv1_CM.so", RTLD_NOW ); + gles = eglstate.libgles1; + } + else + { + if( !eglstate.libgles2 ) + eglstate.libgles2 = dlopen( "libGLESv2.so", RTLD_NOW ); + gles = eglstate.libgles2; + } + + if( gles && ( addr = dlsym( gles, name ))) + return addr; + + if( !egl.GetProcAddress ) + return NULL; + + return egl.GetProcAddress( name ); +} + +/* +========================= +EGL_LoadLibrary +========================= +*/ +void EGL_LoadLibrary( void ) +{ + Sys_LoadLibrary( &egl_info ); +} +#endif diff --git a/engine/platform/android/eglutil.h b/engine/platform/android/eglutil.h new file mode 100644 index 0000000000..a9ba5cf21b --- /dev/null +++ b/engine/platform/android/eglutil.h @@ -0,0 +1,73 @@ +/* +eglutil.h - EGL context utility +Copyright (C) 2023 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. +*/ +#ifndef EGLUTIL_H +#define EGLUTIL_H +#include "platform/platform.h" +#include +#include + +extern struct eglstate_s +{ + qboolean valid, imported; + EGLDisplay dpy; + EGLSurface surface; + EGLContext context; + EGLConfig cfg; + EGLint numCfg; + qboolean support_surfaceless_context; + + const char *extensions; + int gl_attribs[REF_GL_ATTRIBUTES_COUNT]; + qboolean gl_attribs_set[REF_GL_ATTRIBUTES_COUNT]; + EGLint gl_api; + qboolean gles1; + void *libgles1, *libgles2; +} eglstate; + +extern struct eglapi_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 ); + EGLBoolean (*GetConfigAttrib)( EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value ); + EGLDisplay (*GetDisplay)( NativeDisplayType display ); + EGLBoolean (*Initialize)( EGLDisplay dpy, EGLint *major, EGLint *minor ); + EGLBoolean (*Terminate)( EGLDisplay dpy ); + const char *(*QueryString)( EGLDisplay dpy, EGLint name ); + EGLBoolean (*ChooseConfig)( EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config ); + EGLSurface (*CreateWindowSurface)( EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint *attrib_list ); + EGLContext (*CreateContext)( EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list ); + EGLBoolean (*DestroyContext)( EGLDisplay dpy, EGLContext ctx ); + EGLBoolean (*MakeCurrent)( EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx ); + EGLBoolean (*BindAPI)( EGLenum api ); + EGLBoolean (*DestroySurface)( EGLDisplay dpy, EGLSurface surface ); + +} egl; + +void EGL_LoadLibrary( void ); +void * EGL_GetProcAddress( const char *name ); +void EGL_Terminate( void ); +qboolean EGL_ImportContext( void ); +qboolean EGL_CreateContext( void ); +qboolean EGL_UpdateSurface( void *window, qboolean dummy ); +int EGL_GetAttribute( int attr, int *val ); +size_t EGL_GenerateContextConfig( EGLint *attribs, size_t size ); +size_t EGL_GenerateConfig( EGLint *attribs, size_t size ); +int EGL_SetAttribute( int attr, int val ); + +#endif // EGLUTIL_H diff --git a/engine/platform/android/lib_android.c b/engine/platform/android/lib_android.c index a8ba10f385..f58eb6fdcc 100644 --- a/engine/platform/android/lib_android.c +++ b/engine/platform/android/lib_android.c @@ -19,6 +19,12 @@ GNU General Public License for more details. #include "platform/android/lib_android.h" #include "platform/android/dlsym-weak.h" // Android < 5.0 +void *ANDROID_GetServerLibrary( void ) +{ + return svgame.hInstance; +} + + void *ANDROID_LoadLibrary( const char *dllname ) { char path[MAX_SYSPATH]; diff --git a/engine/platform/android/lib_android.h b/engine/platform/android/lib_android.h index 5bead1f332..f3ac9351df 100644 --- a/engine/platform/android/lib_android.h +++ b/engine/platform/android/lib_android.h @@ -20,6 +20,7 @@ GNU General Public License for more details. #define Platform_POSIX_LoadLibrary( x ) ANDROID_LoadLibrary(( x )) #define Platform_POSIX_GetProcAddress( x, y ) ANDROID_GetProcAddress(( x ), ( y )) +void *ANDROID_GetServerLibrary( void ); void *ANDROID_LoadLibrary( const char *dllname ); void *ANDROID_GetProcAddress( void *hInstance, const char *name ); diff --git a/engine/platform/android/snd_opensles.c b/engine/platform/android/snd_opensles.c new file mode 100644 index 0000000000..4cf5bbe061 --- /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 0000000000..4276cdfddb --- /dev/null +++ b/engine/platform/android/vid_android.c @@ -0,0 +1,513 @@ +#include "platform/platform.h" +#if !XASH_SDL +#include "input.h" +#include "client.h" +#include "filesystem.h" +#include "platform/android/android_priv.h" +#include "vid_common.h" +#include +#include +#include "eglutil.h" + + +static struct vid_android_s +{ + qboolean has_context; + ANativeWindow* window; + qboolean nativeegl; +} 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 +static dll_info_t android_info = { "libandroid.so", android_funcs, false }; + +/* +======================== +Android_SwapInterval +======================== +*/ +static void Android_SwapInterval( int interval ) +{ + if( vid_android.nativeegl && eglstate.valid ) + egl.SwapInterval( eglstate.dpy, interval ); +} + +/* +======================== +Android_SetTitle +======================== +*/ +static void Android_SetTitle( const char *title ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.setTitle, (*jni.env)->NewStringUTF( jni.env, title ) ); +} + +/* +======================== +Android_SetIcon +======================== +*/ +static void Android_SetIcon( const char *path ) +{ + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, 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( vid_android.nativeegl && eglstate.valid ) + { + egl.SwapBuffers( eglstate.dpy, eglstate.surface ); + } + else + { + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.swapBuffers ); + } +} + +/* +======================== +Android_UpdateSurface + +Check if we may use native EGL without jni calls +======================== +*/ +void Android_UpdateSurface( surfacestate_t state ) +{ + qboolean active = state == surface_active; + vid_android.nativeegl = false; + + if( glw_state.software || ( eglstate.valid && !eglstate.imported )) + { + if( vid_android.window ) + { + EGL_UpdateSurface( NULL, false ); + nw.release( vid_android.window ); + vid_android.window = NULL; + } + if( state == surface_dummy && glw_state.software ) + return; + // first, ask EGL for surfaceless mode + if( state == surface_dummy && EGL_UpdateSurface( NULL, true )) + { + vid_android.nativeegl = true; + return; + } + + if( state != surface_pause ) + { + EGLint format = WINDOW_FORMAT_RGB_565; + jobject surf; + if( vid_android.window ) + nw.release( vid_android.window ); + surf = (*jni.env)->CallStaticObjectMethod( jni.env, jni.bindcls, jni.getSurface, state ); + Con_DPrintf("Surface handle %p\n", surf); + if( surf ) + { + vid_android.window = nw.fromSurface(jni.env, surf); + Con_DPrintf("NativeWindow %p\n", vid_android.window); + + if( eglstate.valid ) + egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_NATIVE_VISUAL_ID, &format ); + + nw.setBuffersGeometry(vid_android.window, 0, 0, format ); + + (*jni.env)->DeleteLocalRef( jni.env, surf ); + } + } + + if( eglstate.valid && !eglstate.imported ) + { + EGL_UpdateSurface( vid_android.window, state == surface_dummy ); + vid_android.nativeegl = true; + } + return; + } + + if( !vid_android.has_context ) + return; + + (*jni.env)->CallStaticVoidMethod( jni.env, jni.bindcls, jni.toggleEGL, (int)state ); + host.status = HOST_FRAME; // active ? HOST_FRAME : HOST_SLEEP; + + // todo: check opengl context here and set HOST_SLEEP if not + + if( !Sys_CheckParm("-nativeegl") || !active ) + return; // enabled by user + + vid_android.nativeegl = EGL_ImportContext(); + if( vid_android.nativeegl ) + Con_DPrintf( "nativeEGL success\n"); +} + +/* +======================== +Android_GetGLAttribute +======================== +*/ +static int Android_GetGLAttribute( int eglAttr ) +{ + int ret = (*jni.env)->CallStaticIntMethod( jni.env, jni.bindcls, jni.getGLAttribute, eglAttr ); + // Con_Reportf( "Android_GetGLAttribute( %i ) => %i\n", eglAttr, ret ); + return ret; +} + +qboolean R_Init_Video( const int type ) +{ + qboolean retval; + char buf[10]; // Sys_GetParmFromCmdLine + + if( FS_FileExists( GI->iconpath, true ) ) + { + // TODO: convert icon to some android-readable format and place + Android_SetIcon( FS_GetDiskPath( GI->iconpath, false )); + } + + Android_SetTitle( GI->title ); + + VID_StartupGamma(); + + Sys_LoadLibrary( &android_info ); + + switch( type ) + { + case REF_SOFTWARE: + glw_state.software = true; + break; + case REF_GL: + glw_state.software = false; + EGL_LoadLibrary(); + + 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; + + if( !nw.release ) + { + Con_Reportf( S_ERROR "Native software mode unavailiable\n" ); + return false; + } + Android_UpdateSurface( surface_active ); + 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; + + host.status = HOST_FRAME; // where it should we done? We have broken host.status on all non-SDL platforms! + return true; +} + +void R_Free_Video( void ) +{ + // (*jni.env)->CallStaticBooleanMethod( jni.env, jni.bindcls, jni.deleteGLContext ); + + // VID_DestroyWindow (); + + // R_FreeVideoModes(); + Sys_FreeLibrary( &android_info ); + vid_android.has_context = false; + ref.dllFuncs.GL_ClearExtensions(); +} + +void *Android_GetWindow( void ) +{ + EGLint format; + + if( !vid_android.window ) + { + return NULL; + } + + if( !egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_NATIVE_VISUAL_ID, &format) ) + { + Con_Reportf( S_ERROR "eglGetConfigAttrib(VISUAL_ID) returned error: 0x%x\n", egl.GetError() ); + return NULL; + } + + if( nw.setBuffersGeometry( vid_android.window, 0, 0, format ) ) + { + Con_Reportf( S_ERROR "ANativeWindow_setBuffersGeometry returned error\n" ); + return NULL; + } + + return vid_android.window; +} + + +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; + + // create context on egl side by user request + if( !vid_android.has_context && Sys_CheckParm("-egl") ) + { + vid_android.has_context = vid_android.nativeegl = EGL_CreateContext(); + + if( vid_android.has_context ) + Android_UpdateSurface( surface_active ); + else + return false; + } + + if( vid_android.has_context || glw_state.software ) + { + R_ChangeDisplaySettings( 0, 0, WINDOW_MODE_WINDOWED ); // width and height are ignored anyway + return true; + } + + s1 = EGL_GenerateConfig(nAttribs, attribsSize); + s2 = EGL_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, WINDOW_MODE_WINDOWED ); // width and height are ignored anyway + + + if( (*jni.env)->CallStaticBooleanMethod( jni.env, jni.bindcls, jni.createGLContext, attribs, contextAttribs ) ) + { + vid_android.has_context = true; + return true; + } + + return false; +} + +rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) +{ + int render_w, render_h; + + Android_GetScreenRes(&width, &height); + + render_w = width; + render_h = height; + + Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height); + + VID_SetDisplayTransform( &render_w, &render_h ); + + R_SaveVideoMode( width, height, render_w, render_h, true ); + + refState.wideScreen = true; // V_AdjustFov will check for widescreen + + return rserr_ok; +} + +int GL_SetAttribute( int attr, int val ) +{ + return EGL_SetAttribute( attr, val ); +} + +int GL_GetAttribute( int attr, int *val ) +{ + EGLBoolean ret; + + if( eglstate.valid ) + return EGL_GetAttribute( attr, val ); + + 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 +{ + 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 ) + { + // resize event missed? do not resize now, wait for REAL resize event or when java code will be fixed + Con_Printf( S_ERROR "SW_CreateBuffer: buffer too small, need %dx%d, got %dx%d, java part probably sucks\n", width, height, buffer.width, buffer.height ); +#if 0 + if( jni.width < buffer.width ) + jni.width = buffer.width; + if( jni.height < buffer.height ) + jni.width = buffer.height; + VID_SetMode(); + Android_UpdateSurface( true ); +#endif + 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 diff --git a/engine/platform/platform.h b/engine/platform/platform.h index d761fc7b99..73797fc9fd 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -139,6 +139,8 @@ static inline void Platform_Shutdown( void ) #if XASH_SDL SDLash_Shutdown( ); +#elif XASH_ANDROID + Android_Shutdown(); #endif } diff --git a/engine/wscript b/engine/wscript index c9fcb9dd72..9a7983927d 100644 --- a/engine/wscript +++ b/engine/wscript @@ -32,6 +32,9 @@ def options(opt): grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False, help = 'add LLVM libFuzzer [default: %default]' ) + grp.add_option('--enable-android-legacy', action = 'store_true', dest = 'ANDROID_LEGACY', default = False, + help = 'allow build legacy android port without SDL (deprecated, need for renderers debug on ancient devices)') + opt.load('sdl2') def configure(conf): @@ -75,6 +78,8 @@ def configure(conf): conf.define('XASH_SDL', 12) conf.check_cfg(package='sdl', args='--cflags --libs', uselib_store='SDL2' ) conf.env.HAVE_SDL2 = True + elif conf.options.ANDROID_LEGACY: + pass #just test if it can build else: conf.load('sdl2') if not conf.env.HAVE_SDL2: diff --git a/ref/gl/gl_opengl.c b/ref/gl/gl_opengl.c index 7e4a18fa21..acdd114f6f 100644 --- a/ref/gl/gl_opengl.c +++ b/ref/gl/gl_opengl.c @@ -556,55 +556,71 @@ qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char for( func = funcs; func && func->name; func++ ) { // functions are cleared before all the extensions are evaluated - if(( *func->func = (void *)gEngfuncs.GL_GetProcAddress( func->name )) == NULL ) - { - string name; - char *end; - size_t i = 0; + string name; + char *end; + size_t i = 0; + qboolean first_try = true; + // NULL means just func->name, but empty suffix cuts ARB suffix if present #ifdef XASH_GLES - const char *suffixes[] = { "", "EXT", "OES" }; + const char *suffixes[] = { "", "EXT", "OES", NULL, "ARB" }; #else - const char *suffixes[] = { "", "EXT" }; + const char *suffixes[] = { NULL, "", "EXT", "ARB" }; #endif - // HACK: fix ARB names - Q_strncpy( name, func->name, sizeof( name )); - if(( end = Q_strstr( name, "ARB" ))) + // Remove ARB suffix + Q_strncpy( name, func->name, sizeof( name )); + if(( end = Q_strstr( name, "ARB" ))) + { + *end = '\0'; + } + else // I need Q_strstrnul + { + end = name + Q_strlen( name ); + } + + for( ; i < sizeof( suffixes ) / sizeof( suffixes[0] ); i++ ) + { + void *f; + const char *pname = name; + size_t name_len = end - pname; + const char *orig_suffix = func->name + name_len; + + if( suffixes[i] ) { - *end = '\0'; + // if suffix is original suffix, it's handled with NULL + if( orig_suffix[0] && !Q_strcmp( orig_suffix, suffixes[i] )) + continue; + Q_strncat( name, suffixes[i], sizeof( name )); } - else // I need Q_strstrnul + else { - end = name + Q_strlen( name ); - i++; // skip empty suffix + // if original name does not have a suffix, it will be handled with empty suffix + if( !orig_suffix[0] ) + continue; + pname = func->name; } - for( ; i < sizeof( suffixes ) / sizeof( suffixes[0] ); i++ ) + if(( f = gEngfuncs.GL_GetProcAddress( pname ))) { - void *f; + // if we already tried this function, notify about success after previous error from GL_GetProcAddress + if(!first_try) + gEngfuncs.Con_Printf( S_NOTE "found %s\n", pname ); + first_try = false; - Q_strncat( name, suffixes[i], sizeof( name )); - - if(( f = gEngfuncs.GL_GetProcAddress( name ))) - { - // GL_GetProcAddress prints errors about missing functions, so tell user that we found it with different name - gEngfuncs.Con_Printf( S_NOTE "found %s\n", name ); - - *func->func = f; - break; - } - else - { - *end = '\0'; // cut suffix, try next - } + *func->func = f; + break; } - - // not found... - if( i == sizeof( suffixes ) / sizeof( suffixes[0] )) + else { - GL_SetExtension( r_ext, false ); + *end = '\0'; // cut suffix, try next } } + + // not found... + if( i == sizeof( suffixes ) / sizeof( suffixes[0] )) + { + GL_SetExtension( r_ext, false ); + } } #endif diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 18f190a001..37905afced 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -44,6 +44,7 @@ class Android: ndk_rev = 0 is_hardfloat = False clang = False + ndk_binutils = False def __init__(self, ctx, arch, toolchain, api): self.ctx = ctx @@ -214,7 +215,7 @@ def cc(self): if 'CC' in environ: s = environ['CC'] - return '%s --target=%s%d' % (s, self.ndk_triplet(), self.api) + return '%s --target=%s' % (s, self.ndk_triplet()) #%s%d' % (s, self.ndk_triplet(), self.api) return self.gen_toolchain_path() + ('clang' if self.is_clang() else 'gcc') def cxx(self): @@ -225,11 +226,11 @@ def cxx(self): if 'CXX' in environ: s = environ['CXX'] - return '%s --target=%s%d' % (s, self.ndk_triplet(), self.api) + return '%s --target=%s' % (s, self.ndk_triplet()) #%s%d' % (s, self.ndk_triplet(), self.api) return self.gen_toolchain_path() + ('clang++' if self.is_clang() else 'g++') def strip(self): - if self.is_host(): + if self.is_host() and not self.ndk_binutils: environ = getattr(self.ctx, 'environ', os.environ) if 'STRIP' in environ: @@ -240,9 +241,22 @@ def strip(self): return os.path.join(self.gen_binutils_path(), 'llvm-strip') return os.path.join(self.gen_binutils_path(), 'strip') + def objcopy(self): + if self.is_host() and not self.ndk_binutils: + environ = getattr(self.ctx, 'environ', os.environ) + + if 'OBJCOPY' in environ: + return environ['OBJCOPY'] + return 'llvm-objcopy' + + if self.ndk_rev >= 23: + return os.path.join(self.gen_binutils_path(), 'llvm-objcopy') + return os.path.join(self.gen_binutils_path(), 'objcopy') + def system_stl(self): # TODO: proper STL support - return os.path.abspath(os.path.join(self.ndk_home, 'sources', 'cxx-stl', 'system', 'include')) + #return os.path.abspath(os.path.join(self.ndk_home, 'sources', 'cxx-stl', 'system', 'include')) broken with clang-15? TODO: check different ndk versions + return os.path.abspath(os.path.join(self.ndk_home, 'sources', 'cxx-stl', 'stlport', 'stlport')) def libsysroot(self): arch = self.arch @@ -262,8 +276,15 @@ def sysroot(self): def cflags(self, cxx = False): cflags = [] - - if self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX: + android_from_none = False + if self.is_host() and self.is_arm() and self.is_hardfp(): + # clang android target may change with ndk + # override target to none while compiling and + # add some android options manually + android_from_none = True + cflags += ['--target=arm-none-eabi'] + + if self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX and not android_from_none: cflags += ['--sysroot=%s' % (self.sysroot())] else: if self.is_host(): @@ -273,13 +294,18 @@ def cflags(self, cxx = False): ] cflags += ['-I%s' % (self.system_stl())] - if not self.is_clang(): - cflags += ['-DANDROID', '-D__ANDROID__'] + if not self.is_clang() or android_from_none: + cflags += ['-DANDROID', '-D__ANDROID__=1'] + if android_from_none: + cflags += [ '-D__linux__=1', '-fPIC'] # TODO: compare with linux target? if cxx and not self.is_clang() and self.toolchain not in ['4.8','4.9']: cflags += ['-fno-sized-deallocation'] - if self.is_clang(): + if cxx and (self.is_clang() or self.is_host()): + cflags += [ '-Wno-dynamic-exception-spec', '-fno-rtti' ] + + if self.is_clang() or self.is_host(): # stpcpy() isn't available in early Android versions # disable it here so Clang won't use it if self.api < ANDROID_STPCPY_API_MIN: @@ -291,7 +317,7 @@ def cflags(self, cxx = False): cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9'] if self.is_hardfp(): - cflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK'] + cflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK', '-DGLES_SOFTFLOAT'] if self.is_host(): # Clang builtin redefine w/ different calling convention bug @@ -316,15 +342,23 @@ def linkflags(self): linkflags = [] if self.is_host(): linkflags += ['--gcc-toolchain=%s' % self.gen_gcc_toolchain_path()] + linkflags += ['--gcc-install-dir=%s/lib/gcc/%s/4.9/' % (self.gen_gcc_toolchain_path(), self.ndk_triplet())] + if self.ndk_binutils: + linkflags += ['-fuse-ld=%s/bin/%s-ld.bfd' % (self.gen_gcc_toolchain_path(), self.ndk_triplet())] + else: + linkflags += ['-fuse-ld=lld'] + linkflags += ['--unwindlib=none'] + linkflags += ['--rtlib=libgcc'] if self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX: linkflags += ['--sysroot=%s' % (self.sysroot())] elif self.is_host(): linkflags += ['--sysroot=%s/sysroot' % (self.gen_gcc_toolchain_path())] - if self.is_clang() or self.is_host(): - linkflags += ['-fuse-ld=lld'] - else: linkflags += ['-no-canonical-prefixes'] + #if self.is_clang() or self.is_host(): + # linkflags += ['-fuse-ld=lld'] + #else: + linkflags += ['-no-canonical-prefixes'] linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined'] return linkflags @@ -528,6 +562,7 @@ def configure(conf): conf.environ['CC'] = android.cc() conf.environ['CXX'] = android.cxx() conf.environ['STRIP'] = android.strip() + conf.environ['OBJCOPY'] = android.objcopy() conf.env.CFLAGS += android.cflags() conf.env.CXXFLAGS += android.cflags(True) conf.env.LINKFLAGS += android.linkflags() diff --git a/scripts/waifulib/zip.py b/scripts/waifulib/zip.py index c3d0e9ef20..1e0794cac0 100644 --- a/scripts/waifulib/zip.py +++ b/scripts/waifulib/zip.py @@ -44,16 +44,16 @@ def create_zip_archive(self): self.path.get_bld().mkdir() target = self.path.get_bld().make_node(self.name) - tsk = self.create_task('ziparchive', files, target) + self.zip_task = self.create_task('ziparchive', files, target) - setattr(tsk, 'compresslevel', compresslevel) - setattr(tsk, 'relative_to', relative_to) + setattr(self.zip_task, 'compresslevel', compresslevel) + setattr(self.zip_task, 'relative_to', relative_to) try: inst_to = self.install_path self.install_task = self.add_install_files( install_to=inst_to, install_from=target, - chmod=Utils.O644, task=tsk) + chmod=Utils.O644, task=self.zip_task) except AttributeError: pass diff --git a/wscript b/wscript index 4237c466be..a740ab22f3 100644 --- a/wscript +++ b/wscript @@ -2,7 +2,7 @@ # encoding: utf-8 # a1batross, mittorn, 2018 -from waflib import Build, Context, Logs +from waflib import Build, Context, Logs, Options, Configure from waflib.Tools import waf_unit_test import sys import os @@ -69,7 +69,7 @@ SUBDIRS = [ Subproject('dllemu'), # disable only by engine feature, makes no sense to even parse subprojects in dedicated mode - Subproject('3rdparty/extras', lambda x: not x.env.DEDICATED and x.env.DEST_OS != 'android'), + Subproject('3rdparty/extras', lambda x: not x.env.DEDICATED), Subproject('3rdparty/nanogl', lambda x: not x.env.DEDICATED and x.env.NANOGL), Subproject('3rdparty/gl-wes-v2', lambda x: not x.env.DEDICATED and x.env.GLWES), Subproject('3rdparty/gl4es', lambda x: not x.env.DEDICATED and x.env.GL4ES), @@ -104,6 +104,42 @@ REFDLLS = [ RefDll('null', False), ] +def process_extra_projects_opts(ctx): + options, commands, envvars = ctx.parse_cmd_args(allow_unknown = True) + projs = options.EXTRA_PROJECTS + if not projs: + return + for proj in projs.split(','): + ctx.add_subproject(proj) +def process_extra_projects_conf(ctx): + projs = ctx.options.EXTRA_PROJECTS + if not projs: + return + ctx.env.EXTRA_PROJECTS = projs + + for proj in projs.split(','): + tools_orig = ctx.tools.copy() + ctx.add_subproject(proj) + waifulib_path = os.path.join(proj, 'scripts', 'waifulib') + if os.path.isdir(waifulib_path): + for tool in ctx.tools: + if not tool in tools_orig: + if os.path.isfile(os.path.join(waifulib_path, tool['tool'] + '.py')): + tool['tooldir'] = [waifulib_path] + Logs.info('External tooldir set: ' + str(tool)) + + +def process_extra_projects_bld(ctx): + projs = ctx.env.EXTRA_PROJECTS + if not projs: + return + for proj in projs.split(','): + ctx.add_subproject(proj) + + + + + def options(opt): opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msdev msvs msvc subproject cmake') @@ -154,12 +190,15 @@ def options(opt): grp.add_option('--enable-fuzzer', action = 'store_true', dest = 'ENABLE_FUZZER', default = False, help = 'enable building libFuzzer runner [default: %default]' ) + grp.add_option('--extra-projects', action = 'store', dest = 'EXTRA_PROJECTS', default = '', type = 'string', + help = 'add extra projects' ) for i in SUBDIRS: if not i.is_exists(opt): continue opt.add_subproject(i.name) + process_extra_projects_opts(opt) def configure(conf): conf.load('fwgslib reconfigure compiler_optimizations') @@ -201,7 +240,8 @@ def configure(conf): conf.options.GL4ES = True conf.options.GLES3COMPAT = True conf.options.GL = False - conf.define('XASH_SDLMAIN', 1) + if conf.env.HAVE_SDL2: + conf.define('XASH_SDLMAIN', 1) elif conf.env.MAGX: conf.options.SDL12 = True conf.options.NO_VGUI = True @@ -493,6 +533,7 @@ int main(void) { return !opus_custom_encoder_init((OpusCustomEncoder *)1, (const continue conf.add_subproject(i.name) + process_extra_projects_conf(conf) def build(bld): # guard rails to not let install to root @@ -515,3 +556,4 @@ def build(bld): if bld.env.TESTS: bld.add_post_fun(waf_unit_test.summary) bld.add_post_fun(waf_unit_test.set_exit_code) + process_extra_projects_bld(bld)