2
0
mirror of https://github.com/FWGS/xash3d-fwgs synced 2024-11-25 11:19:59 +01:00

Revert "engine: partially remove legacy Android port, in preparation of new port merge"

This reverts commit ef663a8790.
This commit is contained in:
mittorn 2023-10-24 02:36:45 +03:00
parent 8f819a2fde
commit 6f03d31a30
7 changed files with 2015 additions and 3 deletions

View File

@ -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 */

View File

@ -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"

View File

@ -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"

View File

@ -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 <pthread.h>
#include <sys/prctl.h>
#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

View File

@ -0,0 +1,46 @@
#pragma once
#ifndef ANDROID_PRIV_H
#define ANDROID_PRIV_H
#include <EGL/egl.h>
#include <android/log.h>
#include <jni.h>
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

View File

@ -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 <SLES/OpenSLES.h>
#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

View File

@ -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 <android/native_window.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
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