Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ endif()

PROJECT(FS2_Open LANGUAGES ${FSO_LANGUAGES})

if (ANDROID)
message(STATUS "Building for Android (NDK)")
add_definitions(-DPLATFORM_ANDROID)
set(FSO_BUILD_INCLUDED_LIBS ON FORCE)
set(USING_PREBUILT_LIBS ON FORCE)
set(SDL2_USE_PRECOMPILED ON FORCE)
set(OPENAL_USE_PRECOMPILED ON FORCE)
endif()

# Check if the external modules exists
IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/rpavlik-cmake-modules/launcher-templates")
message(FATAL_ERROR "External submodules could not be found. Please make sure you have updated your submodules.")
Expand Down Expand Up @@ -96,8 +105,8 @@ OPTION(FSO_DEVELOPMENT_MODE "Generate binaries in development mode, only use if

OPTION(FSO_BUILD_QTFRED "Build qtFRED2 binary" OFF)

IF(WIN32 OR APPLE)
# On windows and mac the default should be to always build the included libraries
IF(WIN32 OR APPLE OR ANDROID)
# On windows, mac and android the default should be to always build the included libraries
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT ON)
ELSE()
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT OFF)
Expand Down
7 changes: 6 additions & 1 deletion code/cfile/cfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,12 @@ int cfile_init(const char *exe_dir, const char *cdrom_dir)

char buf[CFILE_ROOT_DIRECTORY_LEN];

strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
#ifndef __ANDROID__
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
#else
(void)exe_dir;
strncpy(buf, os_get_working_folder_path().c_str(), CFILE_ROOT_DIRECTORY_LEN - 1);
#endif

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

Expand Down
26 changes: 26 additions & 0 deletions code/ddsutils/ddsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,28 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
return false;
}

#ifdef __ANDROID__
// Auto-determine max texture size based on avail device ram for software decompression
// 128 for <4GB, 256 for < 8GB, 512 for < 12GB and finally 1024
static size_t calculate_resize_max_size()
{
size_t size = 1024;
size_t ram_mib = SDL_GetSystemRAM();

if (ram_mib > 0)
{
if (ram_mib < 4 * 1024) // < 4GB
return 128;
if (ram_mib < 8 * 1024) // < 8GB
return 256;
if (ram_mib < 12 * 1024) // < 12GB
return 512;
}

return size;
}
#endif

// Memory usage for uncompressed textures is quite high. Some MVP assets can
// require well over a GB of VRAM for a single ship after conversion. To help
// alleviate this we need to resize those textures where possible. At 1024x1024
Expand All @@ -85,7 +107,11 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
// returns: number of mipmap levels to skip
static uint conversion_resize(DDS_HEADER &dds_header)
{
#ifdef __ANDROID__
const size_t MAX_SIZE = calculate_resize_max_size();
#else
const size_t MAX_SIZE = 1024;
#endif
uint width, height, depth, offset = 0;

if (dds_header.dwMipMapCount <= 1) {
Expand Down
17 changes: 17 additions & 0 deletions code/external_dll/externalcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ class SCP_ExternalCode
return FALSE;

m_library = SDL_LoadObject(externlib);

#ifdef __ANDROID__
if (m_library == NULL) {
auto android_path = SDL_AndroidGetInternalStoragePath();
if (android_path != nullptr) {
SCP_string android_full_path = android_path;
if (android_full_path.back() != DIR_SEPARATOR_CHAR) {
android_full_path += DIR_SEPARATOR_CHAR;
}
android_full_path += SCP_string("natives") + DIR_SEPARATOR_CHAR + externlib;
#ifndef NDEBUG
mprintf(("Calling SDL_LoadObject with the following path: '%s'\n", android_full_path.c_str()));
#endif
m_library = SDL_LoadObject(android_full_path.c_str());
}
}
#endif

#ifndef NDEBUG
if (m_library == NULL)
Expand Down
4 changes: 4 additions & 0 deletions code/graphics/2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ const auto LightingOption __UNUSED = options::OptionBuilder<int>("Graphics.Light
.parser(parse_lighting_func)
.finish();

#ifndef __ANDROID__
os::ViewportState Gr_configured_window_state = os::ViewportState::Fullscreen;
#else
os::ViewportState Gr_configured_window_state = os::ViewportState::Borderless;
#endif

static bool mode_change_func(os::ViewportState state, bool initial)
{
Expand Down
1 change: 1 addition & 0 deletions code/graphics/opengl/es_compatibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ static inline void glQueryCounter(GLuint id, GLenum target)
#else
(void)id;
#endif
(void)target;
}

// glGetCompressedTexImage not present on GLES and no equivalent
Expand Down
4 changes: 2 additions & 2 deletions code/graphics/opengl/gropengl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <direct.h>
#endif

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

#if !defined __APPLE_CC__ && defined SCP_UNIX
#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX
if (!gladLoadGLXLoader(GL_context->getLoaderFunction(), nullptr, 0)) {
Error(LOCATION, "Failed to load GLX!");
}
Expand Down
130 changes: 130 additions & 0 deletions code/osapi/osapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
#include <sys/types.h>
#endif

#ifdef __ANDROID__
#include <SDL_system.h>
#include <SDL.h>
#include <jni.h>
#include "options/Option.h"
#endif

namespace
{
const char* ORGANIZATION_NAME = "HardLightProductions";
Expand Down Expand Up @@ -840,3 +847,126 @@ SCP_string os_get_config_path(const SCP_string& subpath)
return ss.str();
}

/*
Special functions for Android
*/

#ifdef __ANDROID__
// Helper to get a static method from a java class and clear the exception
// if the method is not found. This is needed to avoid crashing on the next JNI request.
static jmethodID android_get_static_method(JNIEnv* e, jclass cls, const char* name, const char* sig)
{
jmethodID m = e->GetStaticMethodID(cls, name, sig);
if (e->ExceptionCheck()) {
e->ExceptionClear();
m = nullptr;
mprintf(("OSAPI : Method: %s. Not found on GameActivity! Signature: %s. \n", name, sig));
}
return m;
}

SCP_string os_get_working_folder_path()
{
SCP_string wfp{};

//Get the JNI Environment pointer and current Activity instance via SDL
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity();

if (env && activity) {
// Locate the Java class (GameActivity on KnossosNET)
jclass ga = env->GetObjectClass(activity);
if(ga) {
// Get the methodID (activity, methodName, signature);
// "()Ljava/lang/String;" means it takes no paramenters and returns a string
jmethodID methodId = android_get_static_method (env, ga, "getWorkingFolder", "()Ljava/lang/String;");
if (methodId) {
jstring jString = (jstring)env->CallStaticObjectMethod(ga, methodId);
if (jString) {
const char* workingFolder = env->GetStringUTFChars(jString, 0);
wfp = SCP_string(workingFolder);
env->ReleaseStringUTFChars(jString, workingFolder);
env->DeleteLocalRef(jString);
} else {
mprintf(("os_get_working_folder_path: Couldn't get the jString.\n"));
}
} else {
mprintf(("os_get_working_folder_path: Couldn't get the methodID.\n"));
}
env->DeleteLocalRef(ga);
} else {
mprintf(("os_get_working_folder_path: Couldn't get java class.\n"));
}
} else {
mprintf(("os_get_working_folder_path: Couldn't get JNI enviroment or activity.\n"));
}

if (wfp.empty()) {
mprintf(("Couldn't get working folder path from Java class, reverting to SDL default.\n"));
// Fallback to app space on internal storage
const char* fallbackPath = SDL_AndroidGetExternalStoragePath();
if (fallbackPath) {
wfp = SCP_string(fallbackPath);
wfp += "/files/";
}
}

// Ensure path ends with a dir separator
if (!wfp.empty() && (wfp.back() != DIR_SEPARATOR_CHAR)) {
wfp += DIR_SEPARATOR_CHAR;
}
mprintf(("Using working folder: %s\n", wfp.c_str()));
return wfp;
}

void os_touch_overlay_toggle(bool status)
{
//Get the JNI Environment pointer and current Activity instance via SDL
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity();

if (env && activity) {
// Locate the Java class (GameActivity on KnossosNET)
jclass ga = env->GetObjectClass(activity);
if(ga) {
jmethodID methodId = android_get_static_method (env, ga, status ? "enableOverlay" : "disableOverlay", "()V");
if (methodId) {
env->CallStaticVoidMethod(ga, methodId);
} else {
mprintf(("os_touch_overlay_toggle: Couldn't get the methodID.\n"));
}
env->DeleteLocalRef(ga);
} else {
mprintf(("os_touch_overlay_toggle: Couldn't get java class.\n"));
}
} else {
mprintf(("os_touch_overlay_toggle: Couldn't get JNI enviroment or activity.\n"));
}
}

static bool touch_ui_change(bool new_val, bool initial)
{
if (initial) {
return false;
}
os_touch_overlay_toggle(new_val);
return true;
}

static auto TouchOverlayOption = options::OptionBuilder<bool>("Input.TouchOverlay",
std::pair<const char*, int>{"Touch Overlay", -1},
std::pair<const char*, int>{"Enable or disable the Touch Overlay", -1})
.category(std::make_pair("Input", 1827))
.level(options::ExpertLevel::Beginner)
.change_listener(touch_ui_change)
.default_val(true)
.importance(0)
.finish();

void os_touch_overlay_init()
{
os_touch_overlay_toggle(TouchOverlayOption->getValue());
}

#endif

14 changes: 14 additions & 0 deletions code/osapi/osapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ bool os_is_legacy_mode();
*/
SCP_string os_get_config_path(const SCP_string& subpath = "");

/*
Special functions for Android
*/
#ifdef __ANDROID__
// Get working folder absolute path from Android Java Class
SCP_string os_get_working_folder_path();

// Calls to display the touch overlay depending on its last state
void os_touch_overlay_init();

// Enable or disable the touch UI overlay
void os_touch_overlay_toggle(bool status);
#endif

namespace os
{
/**
Expand Down
2 changes: 1 addition & 1 deletion code/sound/openal.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#define _AL_H


#if defined(__APPLE__)
#if defined(__APPLE__) || defined(__ANDROID__)
#include "al.h"
#include "alc.h"
#else
Expand Down
4 changes: 4 additions & 0 deletions code/windows_stub/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ int filelength(int fd)

SCP_string dump_stacktrace()
{
#ifndef __ANDROID__
#ifdef SCP_HAVE_EXECINFO_H
// The following is adapted from here: https://panthema.net/2008/0901-stacktrace-demangled/
const int ADDR_SIZE = 64;
Expand Down Expand Up @@ -139,6 +140,9 @@ SCP_string dump_stacktrace()
#else
return "No stacktrace available";
#endif
#else
return "No stacktrace available";
#endif
}

// retrieve the current working directory
Expand Down
Loading
Loading