Skip to content

Commit 12ff3f2

Browse files
committed
android build support
1 parent 8a174c5 commit 12ff3f2

19 files changed

Lines changed: 284 additions & 19 deletions

File tree

CMakeLists.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ endif()
4747

4848
PROJECT(FS2_Open LANGUAGES ${FSO_LANGUAGES})
4949

50+
if (ANDROID)
51+
message(STATUS "Building for Android (NDK)")
52+
add_definitions(-DPLATFORM_ANDROID)
53+
set(FSO_BUILD_INCLUDED_LIBS ON FORCE)
54+
set(USING_PREBUILT_LIBS ON FORCE)
55+
set(SDL2_USE_PRECOMPILED ON FORCE)
56+
set(OPENAL_USE_PRECOMPILED ON FORCE)
57+
endif()
58+
5059
# Check if the external modules exists
5160
IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/rpavlik-cmake-modules/launcher-templates")
5261
message(FATAL_ERROR "External submodules could not be found. Please make sure you have updated your submodules.")
@@ -96,8 +105,8 @@ OPTION(FSO_DEVELOPMENT_MODE "Generate binaries in development mode, only use if
96105

97106
OPTION(FSO_BUILD_QTFRED "Build qtFRED2 binary" OFF)
98107

99-
IF(WIN32 OR APPLE)
100-
# On windows and mac the default should be to always build the included libraries
108+
IF(WIN32 OR APPLE OR ANDROID)
109+
# On windows, mac and android the default should be to always build the included libraries
101110
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT ON)
102111
ELSE()
103112
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT OFF)

code/cfile/cfile.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ int cfile_init(const char *exe_dir, const char *cdrom_dir)
185185

186186
char buf[CFILE_ROOT_DIRECTORY_LEN];
187187

188-
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
188+
#ifndef __ANDROID__
189+
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
190+
#else
191+
(void)exe_dir;
192+
strncpy(buf, os_get_working_folder_path().c_str(), CFILE_ROOT_DIRECTORY_LEN - 1);
193+
#endif
189194

190195
buf[CFILE_ROOT_DIRECTORY_LEN - 1] = '\0';
191196

code/ddsutils/ddsutils.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,28 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
7474
return false;
7575
}
7676

77+
#ifdef __ANDROID__
78+
// Auto-determine max texture size based on avail device ram for software decompression
79+
// 128 for <4GB, 256 for < 8GB, 512 for < 12GB and finally 1024
80+
static size_t calculate_resize_max_size()
81+
{
82+
size_t size = 1024;
83+
size_t ram_mib = SDL_GetSystemRAM();
84+
85+
if (ram_mib > 0)
86+
{
87+
if (ram_mib < 4 * 1024) // < 4GB
88+
return 128;
89+
if (ram_mib < 8 * 1024) // < 8GB
90+
return 256;
91+
if (ram_mib < 12 * 1024) // < 12GB
92+
return 512;
93+
}
94+
95+
return size;
96+
}
97+
#endif
98+
7799
// Memory usage for uncompressed textures is quite high. Some MVP assets can
78100
// require well over a GB of VRAM for a single ship after conversion. To help
79101
// alleviate this we need to resize those textures where possible. At 1024x1024
@@ -85,7 +107,11 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
85107
// returns: number of mipmap levels to skip
86108
static uint conversion_resize(DDS_HEADER &dds_header)
87109
{
110+
#ifdef __ANDROID__
111+
const size_t MAX_SIZE = calculate_resize_max_size();
112+
#else
88113
const size_t MAX_SIZE = 1024;
114+
#endif
89115
uint width, height, depth, offset = 0;
90116

91117
if (dds_header.dwMipMapCount <= 1) {

code/external_dll/externalcode.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,23 @@ class SCP_ExternalCode
2929
return FALSE;
3030

3131
m_library = SDL_LoadObject(externlib);
32+
33+
#ifdef __ANDROID__
34+
if (m_library == NULL) {
35+
auto android_path = SDL_AndroidGetInternalStoragePath();
36+
if (android_path != nullptr) {
37+
SCP_string android_full_path = android_path;
38+
if (android_full_path.back() != DIR_SEPARATOR_CHAR) {
39+
android_full_path += DIR_SEPARATOR_CHAR;
40+
}
41+
android_full_path += SCP_string("natives") + DIR_SEPARATOR_CHAR + externlib;
42+
#ifndef NDEBUG
43+
mprintf(("Calling SDL_LoadObject with the following path: '%s'\n", android_full_path.c_str()));
44+
#endif
45+
m_library = SDL_LoadObject(android_full_path.c_str());
46+
}
47+
}
48+
#endif
3249

3350
#ifndef NDEBUG
3451
if (m_library == NULL)

code/graphics/2d.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,11 @@ const auto LightingOption __UNUSED = options::OptionBuilder<int>("Graphics.Light
220220
.parser(parse_lighting_func)
221221
.finish();
222222

223+
#ifndef __ANDROID__
223224
os::ViewportState Gr_configured_window_state = os::ViewportState::Fullscreen;
225+
#else
226+
os::ViewportState Gr_configured_window_state = os::ViewportState::Borderless;
227+
#endif
224228

225229
static bool mode_change_func(os::ViewportState state, bool initial)
226230
{

code/graphics/opengl/es_compatibility.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ static inline void glQueryCounter(GLuint id, GLenum target)
353353
#else
354354
(void)id;
355355
#endif
356+
(void)target;
356357
}
357358

358359
// glGetCompressedTexImage not present on GLES and no equivalent

code/graphics/opengl/gropengl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <direct.h>
77
#endif
88

9-
#if !defined __APPLE_CC__ && defined SCP_UNIX
9+
#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX
1010
#include<glad/glad_glx.h>
1111
//Required because X defines none and always, which is used later
1212
#undef None
@@ -1339,7 +1339,7 @@ bool gr_opengl_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps)
13391339
Error(LOCATION, "Failed to load OpenGL!");
13401340
}
13411341

1342-
#if !defined __APPLE_CC__ && defined SCP_UNIX
1342+
#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX
13431343
if (!gladLoadGLXLoader(GL_context->getLoaderFunction(), nullptr, 0)) {
13441344
Error(LOCATION, "Failed to load GLX!");
13451345
}

code/osapi/osapi.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
#include <sys/types.h>
2929
#endif
3030

31+
#ifdef __ANDROID__
32+
#include <SDL_system.h>
33+
#include <SDL.h>
34+
#include <jni.h>
35+
#include "options/Option.h"
36+
#endif
37+
3138
namespace
3239
{
3340
const char* ORGANIZATION_NAME = "HardLightProductions";
@@ -840,3 +847,126 @@ SCP_string os_get_config_path(const SCP_string& subpath)
840847
return ss.str();
841848
}
842849

850+
/*
851+
Special functions for Android
852+
*/
853+
854+
#ifdef __ANDROID__
855+
// Helper to get a static method from a java class and clear the exception
856+
// if the method is not found. This is needed to avoid crashing on the next JNI request.
857+
static jmethodID android_get_static_method(JNIEnv* e, jclass cls, const char* name, const char* sig)
858+
{
859+
jmethodID m = e->GetStaticMethodID(cls, name, sig);
860+
if (e->ExceptionCheck()) {
861+
e->ExceptionClear();
862+
m = nullptr;
863+
mprintf(("OSAPI : Method: %s. Not found on GameActivity! Signature: %s. \n", name, sig));
864+
}
865+
return m;
866+
}
867+
868+
SCP_string os_get_working_folder_path()
869+
{
870+
SCP_string wfp{};
871+
872+
//Get the JNI Environment pointer and current Activity instance via SDL
873+
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
874+
jobject activity = (jobject)SDL_AndroidGetActivity();
875+
876+
if (env && activity) {
877+
// Locate the Java class (GameActivity on KnossosNET)
878+
jclass ga = env->GetObjectClass(activity);
879+
if(ga) {
880+
// Get the methodID (activity, methodName, signature);
881+
// "()Ljava/lang/String;" means it takes no paramenters and returns a string
882+
jmethodID methodId = android_get_static_method (env, ga, "getWorkingFolder", "()Ljava/lang/String;");
883+
if (methodId) {
884+
jstring jString = (jstring)env->CallStaticObjectMethod(ga, methodId);
885+
if (jString) {
886+
const char* workingFolder = env->GetStringUTFChars(jString, 0);
887+
wfp = SCP_string(workingFolder);
888+
env->ReleaseStringUTFChars(jString, workingFolder);
889+
env->DeleteLocalRef(jString);
890+
} else {
891+
mprintf(("os_get_working_folder_path: Couldn't get the jString.\n"));
892+
}
893+
} else {
894+
mprintf(("os_get_working_folder_path: Couldn't get the methodID.\n"));
895+
}
896+
env->DeleteLocalRef(ga);
897+
} else {
898+
mprintf(("os_get_working_folder_path: Couldn't get java class.\n"));
899+
}
900+
} else {
901+
mprintf(("os_get_working_folder_path: Couldn't get JNI enviroment or activity.\n"));
902+
}
903+
904+
if (wfp.empty()) {
905+
mprintf(("Couldn't get working folder path from Java class, reverting to SDL default.\n"));
906+
// Fallback to app space on internal storage
907+
const char* fallbackPath = SDL_AndroidGetExternalStoragePath();
908+
if (fallbackPath) {
909+
wfp = SCP_string(fallbackPath);
910+
wfp += "/files/";
911+
}
912+
}
913+
914+
// Ensure path ends with a dir separator
915+
if (!wfp.empty() && (wfp.back() != DIR_SEPARATOR_CHAR)) {
916+
wfp += DIR_SEPARATOR_CHAR;
917+
}
918+
mprintf(("Using working folder: %s\n", wfp.c_str()));
919+
return wfp;
920+
}
921+
922+
void os_touch_overlay_toggle(bool status)
923+
{
924+
//Get the JNI Environment pointer and current Activity instance via SDL
925+
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
926+
jobject activity = (jobject)SDL_AndroidGetActivity();
927+
928+
if (env && activity) {
929+
// Locate the Java class (GameActivity on KnossosNET)
930+
jclass ga = env->GetObjectClass(activity);
931+
if(ga) {
932+
jmethodID methodId = android_get_static_method (env, ga, status ? "enableOverlay" : "disableOverlay", "()V");
933+
if (methodId) {
934+
env->CallStaticVoidMethod(ga, methodId);
935+
} else {
936+
mprintf(("os_touch_overlay_toggle: Couldn't get the methodID.\n"));
937+
}
938+
env->DeleteLocalRef(ga);
939+
} else {
940+
mprintf(("os_touch_overlay_toggle: Couldn't get java class.\n"));
941+
}
942+
} else {
943+
mprintf(("os_touch_overlay_toggle: Couldn't get JNI enviroment or activity.\n"));
944+
}
945+
}
946+
947+
static bool touch_ui_change(bool new_val, bool initial)
948+
{
949+
if (initial) {
950+
return false;
951+
}
952+
os_touch_overlay_toggle(new_val);
953+
return true;
954+
}
955+
956+
static auto TouchOverlayOption = options::OptionBuilder<bool>("Input.TouchOverlay",
957+
std::pair<const char*, int>{"Touch Overlay", -1},
958+
std::pair<const char*, int>{"Enable or disable the Touch Overlay", -1})
959+
.category(std::make_pair("Input", 1827))
960+
.level(options::ExpertLevel::Beginner)
961+
.change_listener(touch_ui_change)
962+
.default_val(true)
963+
.importance(0)
964+
.finish();
965+
966+
void os_touch_overlay_init()
967+
{
968+
os_touch_overlay_toggle(TouchOverlayOption->getValue());
969+
}
970+
971+
#endif
972+

code/osapi/osapi.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ bool os_is_legacy_mode();
9191
*/
9292
SCP_string os_get_config_path(const SCP_string& subpath = "");
9393

94+
/*
95+
Special functions for Android
96+
*/
97+
#ifdef __ANDROID__
98+
// Get working folder absolute path from Android Java Class
99+
SCP_string os_get_working_folder_path();
100+
101+
// Calls to display the touch overlay depending on its last state
102+
void os_touch_overlay_init();
103+
104+
// Enable or disable the touch UI overlay
105+
void os_touch_overlay_toggle(bool status);
106+
#endif
107+
94108
namespace os
95109
{
96110
/**

code/sound/openal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#define _AL_H
44

55

6-
#if defined(__APPLE__)
6+
#if defined(__APPLE__) || defined(__ANDROID__)
77
#include "al.h"
88
#include "alc.h"
99
#else

0 commit comments

Comments
 (0)