Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
12ff3f2
android build support
Shivansps Jun 8, 2026
c8f6635
Merge remote-tracking branch 'origin/master' into android-build
Shivansps Jun 14, 2026
4a3c983
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Jul 5, 2026
3ea6bba
Update es_compatibility.h
Shivansps Jul 5, 2026
fda9f4f
Support reporting flags in android
Shivansps Jul 12, 2026
40106bd
include pref_path in json v2
Shivansps Jul 16, 2026
cdc1cb1
Merge branch 'master' into android-build
Shivansps Jul 16, 2026
1faf5c1
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Jul 18, 2026
f2af415
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Jul 22, 2026
19c99df
add vulkan headers
Shivansps Jul 22, 2026
7bf1449
shaderc includes
Shivansps Jul 22, 2026
28a16b7
remove headers
Shivansps Jul 23, 2026
0ca0a00
force use prebuilt vulkan
Shivansps Jul 23, 2026
2d64531
fix vulkan inverted screen
Shivansps Jul 23, 2026
6c55840
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Jul 23, 2026
444f5f5
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Jul 25, 2026
6f96a61
fix find sdl3 precompiled android
Shivansps Jul 25, 2026
f53a954
Update CMakeLists.txt
Shivansps Jul 25, 2026
c85b28a
replace sdl2 android function names for sdl3 versions
Shivansps Jul 25, 2026
b381b7d
change sdl includes in osapi
Shivansps Jul 25, 2026
8ddb264
fix sdl3 includes, again
Shivansps Jul 25, 2026
a2fe9db
rename function to sdl3 name
Shivansps Jul 25, 2026
733d563
remove unused definition
Shivansps Jul 26, 2026
15c8c3a
copy libshaderc_shared.so to output in android
Shivansps Jul 26, 2026
a1eb460
do not look for vulkan-loader in android
Shivansps Jul 26, 2026
3220a78
revert changes to SDL2.cmake
Shivansps Jul 26, 2026
6cbcaef
add comment to vulkan pretransform setup
Shivansps Jul 26, 2026
52b0a97
cleanup maxsize function
Shivansps Jul 26, 2026
72ad41b
rename new android functions in osapi
Shivansps Jul 26, 2026
a17cfdd
android trap back button
Shivansps Jul 29, 2026
d1294e7
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Aug 1, 2026
99acbb7
Merge remote-tracking branch 'upstream/master' into android-build
Shivansps Aug 6, 2026
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)")
set(FSO_BUILD_INCLUDED_LIBS ON FORCE)
set(USING_PREBUILT_LIBS ON FORCE)
set(SDL3_USE_PRECOMPILED ON FORCE)
set(OPENAL_USE_PRECOMPILED ON FORCE)
set(USING_PREBUILT_VULKAN ON FORCE)
endif()

# Check if the external modules exists
IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib/libRocket/Build")
message(FATAL_ERROR "External submodules could not be found. Please make sure you have updated your submodules.")
Expand Down Expand Up @@ -99,8 +108,8 @@ OPTION(FSO_BUILD_TESTS "Build unit tests" OFF)

OPTION(FSO_DEVELOPMENT_MODE "Generate binaries in development mode, only use if you know what you're doing!" 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_android_get_working_folder_path().c_str(), CFILE_ROOT_DIRECTORY_LEN - 1);
#endif

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

Expand Down
112 changes: 70 additions & 42 deletions code/cmdline/cmdline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const char *cmdline_arg_types[] =
enum class flag_output_type {
Binary,
Json_V1,
Json_V2,
};

// variables
Expand Down Expand Up @@ -1357,51 +1358,58 @@ static void handle_unix_modlist(char **modlist, size_t *len)

// external entry point into this modules

static json_t* json_get_v1() {
auto root = json_object();
static void json_add_version(json_t* root) {
auto version_obj = json_object();

{
auto version_obj = json_object();
json_object_set_new(version_obj, "full", json_string(FS_VERSION_FULL));
json_object_set_new(version_obj, "major", json_integer(FS_VERSION_MAJOR));
json_object_set_new(version_obj, "minor", json_integer(FS_VERSION_MINOR));
json_object_set_new(version_obj, "build", json_integer(FS_VERSION_BUILD));

json_object_set_new(version_obj, "has_revision", json_boolean(FS_VERSION_HAS_REVIS));
json_object_set_new(version_obj, "revision", json_integer(FS_VERSION_REVIS));
json_object_set_new(version_obj, "revision_str", json_string(FS_VERSION_REVIS_STR));

json_object_set_new(version_obj, "full", json_string(FS_VERSION_FULL));
json_object_set_new(version_obj, "major", json_integer(FS_VERSION_MAJOR));
json_object_set_new(version_obj, "minor", json_integer(FS_VERSION_MINOR));
json_object_set_new(version_obj, "build", json_integer(FS_VERSION_BUILD));
json_object_set_new(root, "version", version_obj);
}

json_object_set_new(version_obj, "has_revision", json_boolean(FS_VERSION_HAS_REVIS));
json_object_set_new(version_obj, "revision", json_integer(FS_VERSION_REVIS));
json_object_set_new(version_obj, "revision_str", json_string(FS_VERSION_REVIS_STR));
static void json_add_easy_flags(json_t* root) {
auto easy_array = json_array();

json_object_set_new(root, "version", version_obj);
for (auto& easy_flag : easy_flags) {
json_array_append_new(easy_array, json_string(easy_flag.name));
}
{
auto easy_array = json_array();

for (auto& easy_flag : easy_flags) {
json_array_append_new(easy_array, json_string(easy_flag.name));
}
json_object_set_new(root, "easy_flags", easy_array);
}

json_object_set_new(root, "easy_flags", easy_array);
static void json_add_flags(json_t* root) {
auto flags_array = json_array();

for (auto& flag : exe_params) {
auto flag_obj = json_object();

json_object_set_new(flag_obj, "name", json_string(flag.name));
json_object_set_new(flag_obj, "description", json_string(flag.desc));
json_object_set_new(flag_obj, "fso_only", json_boolean(flag.fso_only));
json_object_set_new(flag_obj, "on_flags", json_integer(flag.on_flags));
json_object_set_new(flag_obj, "off_flags", json_integer(flag.off_flags));
json_object_set_new(flag_obj, "type", json_string(flag.type));
json_object_set_new(flag_obj, "web_url", json_string(flag.web_url));

json_array_append_new(flags_array, flag_obj);
}
{
auto flags_array = json_array();

for (auto& flag : exe_params) {
auto flag_obj = json_object();
json_object_set_new(root, "flags", flags_array);
}

json_object_set_new(flag_obj, "name", json_string(flag.name));
json_object_set_new(flag_obj, "description", json_string(flag.desc));
json_object_set_new(flag_obj, "fso_only", json_boolean(flag.fso_only));
json_object_set_new(flag_obj, "on_flags", json_integer(flag.on_flags));
json_object_set_new(flag_obj, "off_flags", json_integer(flag.off_flags));
json_object_set_new(flag_obj, "type", json_string(flag.type));
json_object_set_new(flag_obj, "web_url", json_string(flag.web_url));
static json_t* json_get_v1() {
auto root = json_object();

json_array_append_new(flags_array, flag_obj);
}
json_add_version(root);
json_add_easy_flags(root);
json_add_flags(root);

json_object_set_new(root, "flags", flags_array);
}
{
auto caps_array = json_array();

Expand Down Expand Up @@ -1512,6 +1520,18 @@ static json_t* json_get_v1() {
return root;
}

// json_v2 is a minimal json string to report supported flags only
static json_t* json_get_v2() {
auto root = json_object();

json_add_version(root);
json_add_easy_flags(root);
json_add_flags(root);
json_object_set_new(root, "pref_path", json_string(os_get_config_path().c_str()));

return root;
}

static void write_flags_file() {
FILE *fp = fopen("flags.lch","w");

Expand Down Expand Up @@ -1569,6 +1589,8 @@ static flag_output_type get_flags_output_type() {
return flag_output_type::Binary;
} else if (type == "json_v1") {
return flag_output_type::Json_V1;
} else if (type == "json_v2") {
return flag_output_type::Json_V2;
} else {
// This is supposed to make it easy for the launcher to recognize an unsupported type
printf("OUTPUT TYPE NOT SUPPORTED!\n");
Expand All @@ -1579,17 +1601,23 @@ static flag_output_type get_flags_output_type() {

static void write_flags() {
auto type = get_flags_output_type();
switch(type) {
case flag_output_type::Binary:
write_flags_file();
break;
case flag_output_type::Json_V1:
json_t* root = json_get_v1();

json_dumpf(root, stdout, JSON_INDENT(4));
json_decref(root);
break;
if (type == flag_output_type::Binary) {
write_flags_file();
return;
}

json_t* root = (type == flag_output_type::Json_V1) ? json_get_v1() : json_get_v2();

#ifdef __ANDROID__
char* dumped = json_dumps(root, JSON_INDENT(4));
os_android_set_flags_string(dumped ? dumped : "");
free(dumped);
#else
json_dumpf(root, stdout, JSON_INDENT(4));
#endif

json_decref(root);
}

bool SetCmdlineParams()
Expand Down
25 changes: 24 additions & 1 deletion code/ddsutils/ddsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,28 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
return false;
}


// Auto-determine max texture size based on avail device ram for software decompression
// Desktop OS: 1024
// Android OS: 128 for <4GB, 256 for < 8GB, 512 for < 12GB and finally 1024
static size_t calculate_resize_max_size()
{
#ifdef __ANDROID__
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;
}
#endif
return 1024;
}

// 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 @@ -79,7 +101,8 @@ static bool conversion_needed(const DDS_HEADER &dds_header)
// returns: number of mipmap levels to skip
static uint conversion_resize(DDS_HEADER &dds_header)
{
const size_t MAX_SIZE = 1024;
const size_t MAX_SIZE = calculate_resize_max_size();

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 @@ -28,6 +28,23 @@ class SCP_ExternalCode
return FALSE;

m_library = SDL_LoadObject(externlib);

#ifdef __ANDROID__
if (m_library == NULL) {
auto android_path = SDL_GetAndroidInternalStoragePath();
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

// check full path as a fallback for our own libraries
// NOTE: basePath is assumed to have a trailing slash!
Expand Down
4 changes: 4 additions & 0 deletions code/graphics/2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,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
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 @@ -1394,7 +1394,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
18 changes: 17 additions & 1 deletion code/graphics/vulkan/VulkanRendererSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,23 @@ bool VulkanRenderer::createSwapChain(const PhysicalDeviceValues& deviceValues, v
createInfo.imageSharingMode = vk::SharingMode::eExclusive;
}

createInfo.preTransform = deviceValues.surfaceCapabilities.currentTransform;
// On desktop the surface always reports eIdentity here.
// On Android the presentation engine can report a non-identity currentTransform
// (the compositor pre-rotation for the physical panel orientation). Passing that
// value back as preTransform tells the engine that we already rendered our content
// in that orientation, so it applies the rotation/flip a second time on top of the
// fixed orientation FSO actually renders in - the result is the image being transformed twice.
//
// So we request an identity preTransform whenever the surface supports it,
// and only fall back to currentTransform when it does not.
auto supported = deviceValues.surfaceCapabilities.supportedTransforms;
if (supported & vk::SurfaceTransformFlagBitsKHR::eIdentity) {
createInfo.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
}
else {
createInfo.preTransform = deviceValues.surfaceCapabilities.currentTransform;
}

createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
createInfo.presentMode = choosePresentMode(deviceValues);
createInfo.clipped = true;
Expand Down
Loading