/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ // System functions for Android OS. // Based on sys_linux.c #include #include #include #include #include #include #include #include #include #include // #include // #include #include #include #include #include #include #include #include #include #include "quakedef.h" #define LOG_TAG "Quake sys_android" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) qboolean isDedicated; int noconinput = 0; int nostdout = 0; // Look for data on either the sdcard or the internal data store. // (We look at the sdcard first static const char *basedir1 = "/sdcard/data/quake"; static const char *basedir2 = "/data/quake"; static const char *cachedir = "/tmp"; cvar_t sys_linerefresh = CVAR2("sys_linerefresh","0");// set for entity display // ======================================================================= // General routines // ======================================================================= void Sys_DebugNumber(int y, int val) { } /* void Sys_Printf (char *fmt, ...) { va_list argptr; char text[1024]; va_start (argptr,fmt); vsprintf (text,fmt,argptr); va_end (argptr); fprintf(stderr, "%s", text); Con_Print (text); } void Sys_Printf (char *fmt, ...) { va_list argptr; char text[1024], *t_p; int l, r; if (nostdout) return; va_start (argptr,fmt); vsprintf (text,fmt,argptr); va_end (argptr); l = strlen(text); t_p = text; // make sure everything goes through, even though we are non-blocking while (l) { r = write (1, text, l); if (r != l) sleep (0); if (r > 0) { t_p += r; l -= r; } } } */ #define USE_PMPEVENT void Sys_Printf (const char *fmt, ...) { va_list argptr; char text[2048]; unsigned char *p; va_start (argptr,fmt); vsnprintf (text, sizeof(text), fmt,argptr); va_end (argptr); text[sizeof(text)-1] = 0; LOGI("%s", text); #ifdef USE_PMPEVENT PMPEVENT(("%s", text)); #else if (nostdout) return; for (p = (unsigned char *)text; *p; p++) if ((*p > 128 || *p < 32) && *p != 10 && *p != 13 && *p != 9) printf("[%02x]", *p); else putc(*p, stdout); #endif } qboolean soft_quit; void Sys_Quit (void) { Host_Shutdown(); #ifdef USE_PMPEVENT PMPERROR(("Sys_Quit - exiting.")); #else printf("Sys_Quit - exiting.\n"); #endif // fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); if (soft_quit) { return; } exit(0); } void Sys_Init(void) { } void Sys_Error (const char *error, ...) { va_list argptr; char string[1024]; // change stdin to non blocking // fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); va_start (argptr,error); vsprintf (string,error,argptr); va_end (argptr); #ifdef USE_PMPEVENT PMPERROR(("Error: %s\n", string)); #else fprintf(stderr, "Error: %s\n", string); #endif Host_Shutdown (); #ifdef USE_PMPEVENT PMPERROR(("Sys_Error - exiting.")); #else printf("Sys_Error - exiting.\n"); #endif exit (1); } void Sys_Warn (const char *warning, ...) { va_list argptr; char string[1024]; va_start (argptr,warning); vsprintf (string,warning,argptr); va_end (argptr); #ifdef USE_PMPEVENT PMPWARNING(("Warning: %s", string)); #else fprintf(stderr, "Warning: %s\n", string); #endif } /* ============ Sys_FileTime returns -1 if not present ============ */ int Sys_FileTime (const char *path) { struct stat buf; if (stat (path,&buf) == -1) return -1; return buf.st_mtime; } void Sys_mkdir (const char *path) { mkdir (path, 0777); } int Sys_FileOpenRead (const char *path, int *handle) { int h; struct stat fileinfo; h = open (path, O_RDONLY, 0666); *handle = h; if (h == -1) return -1; if (fstat (h,&fileinfo) == -1) Sys_Error ("Error fstating %s", path); return fileinfo.st_size; } int Sys_FileOpenWrite (const char *path) { int handle; umask (0); handle = open(path,O_RDWR | O_CREAT | O_TRUNC , 0666); if (handle == -1) Sys_Error ("Error opening %s: %s", path,strerror(errno)); return handle; } int Sys_FileWrite (int handle, const void *src, int count) { return write (handle, src, count); } void Sys_FileClose (int handle) { close (handle); } void Sys_FileSeek (int handle, int position) { lseek (handle, position, SEEK_SET); } int Sys_FileRead (int handle, void *dest, int count) { return read (handle, dest, count); } void Sys_DebugLog(const char *file, char *fmt, ...) { va_list argptr; static char data[1024]; int fd; va_start(argptr, fmt); vsprintf(data, fmt, argptr); va_end(argptr); // fd = open(file, O_WRONLY | O_BINARY | O_CREAT | O_APPEND, 0666); fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0666); write(fd, data, strlen(data)); close(fd); } void Sys_EditFile(const char *filename) { char cmd[256]; char *term; const char *editor; term = getenv("TERM"); if (term && !strcmp(term, "xterm")) { editor = getenv("VISUAL"); if (!editor) editor = getenv("EDITOR"); if (!editor) editor = getenv("EDIT"); if (!editor) editor = "vi"; sprintf(cmd, "xterm -e %s %s", editor, filename); system(cmd); } } double Sys_FloatTime (void) { struct timeval tp; struct timezone tzp; static int secbase; gettimeofday(&tp, &tzp); if (!secbase) { secbase = tp.tv_sec; return tp.tv_usec/1000000.0; } return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; } // ======================================================================= // Sleeps for microseconds // ======================================================================= static volatile int oktogo; void alarm_handler(int x) { oktogo=1; } void Sys_LineRefresh(void) { } void floating_point_exception_handler(int whatever) { // Sys_Warn("floating point exception\n"); signal(SIGFPE, floating_point_exception_handler); } char *Sys_ConsoleInput(void) { #if 0 static char text[256]; int len; if (cls.state == ca_dedicated) { len = read (0, text, sizeof(text)); if (len < 1) return NULL; text[len-1] = 0; // rip off the /n and terminate return text; } #endif return NULL; } #if !id386 void Sys_HighFPPrecision (void) { } void Sys_LowFPPrecision (void) { } #endif int skipframes; // The following APIs are called from the Java activity double g_oldtime; extern int scr_width; extern int scr_height; qboolean direxists(const char* path) { struct stat sb; if(stat(path, &sb)) { return 0; // error } if(sb.st_mode & S_IFDIR) { return 1; } return 0; } // Remove all files in path. Recurses into subdirectories void rmDir(const char* path) { DIR* dir = opendir(path); if(!dir) { return; } struct dirent * dp; while((dp = readdir(dir)) != NULL) { const char* name = dp->d_name; if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { continue; } char filePath[1024]; if ((int) (sizeof(filePath)-1) < snprintf(filePath, sizeof(filePath), "%s/%s", path, name)) { continue; // buffer overflow } if(direxists(filePath)) { rmDir(filePath); } else { unlink(filePath); } } closedir(dir); rmdir(path); } // Increment this number whenever the data format of any of the files stored in glquake changes: typedef unsigned long GLCacheVersion; static const GLCacheVersion kCurrentCacheVersion = 0x3a914000; // The numbers mean nothing special // #define FORCE_INVALIDATE_CACHE // Useful for testing #define GLQUAKE_RELPATH "/id1/glquake" void CheckGLCacheVersion(const char* baseDir) { char cachePath[1024]; if ((int) (sizeof(cachePath)-1) < snprintf(cachePath, sizeof(cachePath), "%s" GLQUAKE_RELPATH "/cacheversion", baseDir)) { return; // buffer overflow } bool validCache = false; { GLCacheVersion vernum = 0; FILE* f = fopen(cachePath, "rb"); if (f) { if (1 == fread(&vernum, sizeof(vernum), 1, f)) { if (vernum == kCurrentCacheVersion) { validCache = true; } } fclose(f); } } #ifdef FORCE_INVALIDATE_CACHE validCache = false; #endif if(!validCache) { PMPLOG(("Invalidating glquake cache.")); char cacheDirPath[1024]; if ( (int)(sizeof(cacheDirPath)-1) < snprintf(cacheDirPath, sizeof(cacheDirPath), "%s" GLQUAKE_RELPATH, baseDir)) { return; // Ran out ot memory } rmDir(cacheDirPath); Sys_mkdir(cacheDirPath); FILE* f = fopen(cachePath, "wb"); if (f) { GLCacheVersion vernum = kCurrentCacheVersion; fwrite(&vernum, sizeof(vernum), 1, f); fclose(f); } else { PMPLOG(("Could not write %s %d.\n", cachePath, errno)); } } } static int gArgc; static char** gArgv; void AndroidInitArgs(int argc, char** argv) { gArgc = argc; gArgv = argv; } static qboolean gDoubleInitializeGuard; static qboolean gInitialized; void GL_ReInit(); #if !defined(__clang__) bool AndroidInit() #else extern "C" bool AndroidInit_LLVM() #endif { PMPLOG(("AndroidInit")); PMPLOG(("This function was compiled on " __DATE__ " at " __TIME__)); if (gDoubleInitializeGuard && gInitialized) { GL_ReInit(); } gDoubleInitializeGuard = true; return true; } // Note: Needs a valid OpenGL context void AndroidInit2(int width, int height) { PMPLOG(("AndroidInit2 %d,%d", width, height)); gInitialized = true; PMPBEGIN(("AndroidInit2")); quakeparms_t parms; int j; int c = 0; const char* v[] = {"quake", (char*) 0}; scr_width = width; scr_height = height; // static char cwd[1024]; // signal(SIGFPE, floating_point_exception_handler); // signal(SIGFPE, SIG_IGN); memset(&parms, 0, sizeof(parms)); if (gArgc) { COM_InitArgv(gArgc, (const char**) gArgv); } else { COM_InitArgv(c, (const char**) v); } parms.argc = com_argc; parms.argv = com_argv; parms.memsize = 16*1024*1024; j = COM_CheckParm("-mem"); if (j) parms.memsize = (int) (Q_atof(com_argv[j+1]) * 1024 * 1024); parms.membase = malloc (parms.memsize); const char* basedir = basedir2; if(direxists(basedir1)) { basedir = basedir1; } else if(direxists(basedir2)) { basedir = basedir2; } else { Sys_Error("Could not find data directories %s or %s", basedir1, basedir2); } parms.basedir = basedir; CheckGLCacheVersion(basedir); // caching is disabled by default, use -cachedir to enable // parms.cachedir = cachedir; #if 0 // FNDELAY not implemented noconinput = COM_CheckParm("-noconinput"); if (!noconinput) fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); #endif if (COM_CheckParm("-nostdout")) nostdout = 1; Sys_Init(); Host_Init(&parms); g_oldtime = Sys_FloatTime (); PMPEND(("AndroidInit2")); } static int currentFrame; frameTime fastestFrame; frameTime slowestFrame; void InitFrameTimes() { currentFrame = 0; fastestFrame.time = 1e6; fastestFrame.frame = 0; slowestFrame.time = -1; slowestFrame.frame = 0; } static void UpdateFrameTimes(float time) { if (currentFrame > 0) { if (fastestFrame.time > time) { fastestFrame.time = time; fastestFrame.frame = currentFrame; } if (slowestFrame.time < time) { slowestFrame.time = time; slowestFrame.frame = currentFrame; } } currentFrame++; } int AndroidStepImp(int width, int height) { // PMPBEGIN(("AndroidStep")); double time, newtime; static int TotalCount = 0; static double TotalFPS = 0.0; if(!gInitialized) AndroidInit2(width, height); scr_width = width; scr_height = height; // find time spent rendering last frame newtime = Sys_FloatTime (); time = newtime - g_oldtime; UpdateFrameTimes(time); #if 0 // Disable the following because given a series of Ti representing time spent per frame // 1/(sum(Ti)/n) isn't quite the same as sum(1/Ti)/n, especially when Ti has large variance. // See LOGI in host.cpp::Host_Frame for better implementation double fps = 1.0/time; if (fps > 0.0 && fps < 200.0) { // Sometimes it TotalCount += 1; TotalFPS += fps; LOGI("Quake fps: %3.2lf, Average: %3.2lf", fps, TotalFPS/TotalCount); } #endif Host_Frame(time); g_oldtime = newtime; // PMPEND(("AndroidStep")); return key_dest == key_game; } #if !defined(__clang__) int AndroidStep(int width, int height) #else extern "C" int AndroidStep_LLVM(int width, int height) #endif { for(;;) { host_framethrottled = false; int result = AndroidStepImp(width, height); if (!host_framethrottled) { return result; } usleep(1000); //LOGI("%s", "host_framethrottled"); } } extern void Host_Quit(); #if !defined(__clang__) void AndroidQuit() { #else extern "C" void AndroidQuit_LLVM() { #endif soft_quit = true; Host_Quit(); soft_quit = false; // In case we live on after returning. }