From e4dba13851b4a887bdb2abfc773d5ba0364936b1 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Wed, 4 Mar 2026 03:23:12 -0500 Subject: [PATCH] added fps option in settings --- android/app/src/main/cpp/xemu_android.cpp | 14 ++++++++ .../app/src/main/cpp/xemu_settings_android.cc | 11 ++++++ .../com/izzy2lost/x1box/SettingsActivity.kt | 13 +++++++ .../src/main/res/layout/activity_settings.xml | 34 +++++++++++++++++++ android/app/src/main/res/values/strings.xml | 3 ++ ui/xemu.c | 32 ++++++++++++++++- 6 files changed, 106 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/cpp/xemu_android.cpp b/android/app/src/main/cpp/xemu_android.cpp index 1de633707d..a899a82435 100644 --- a/android/app/src/main/cpp/xemu_android.cpp +++ b/android/app/src/main/cpp/xemu_android.cpp @@ -575,6 +575,7 @@ cleanup: struct EmulatorSettings { int surface_scale = 1; // 1, 2, or 3 + int frame_rate_limit = 60; // 30 or 60 std::string tcg_thread = "multi"; // "single" or "multi" bool use_dsp = false; bool hrtf = true; @@ -658,6 +659,13 @@ static bool WriteConfigToml(const std::string& config_path, perf->insert_or_assign("hard_fpu", settings.hard_fpu); android->insert_or_assign("tcg_thread", (settings.tcg_thread == "single") ? "single" : "multi"); + { + int frame_rate_limit = settings.frame_rate_limit; + if (frame_rate_limit != 30 && frame_rate_limit != 60) { + frame_rate_limit = 60; + } + android->insert_or_assign("frame_rate_limit", frame_rate_limit); + } if (!audio_vp->contains("num_workers")) { audio_vp->insert_or_assign("num_workers", 0); } @@ -790,6 +798,12 @@ static SetupFiles SyncSetupFiles() { EmulatorSettings emuSettings; emuSettings.surface_scale = GetPrefInt(env, activity, "setting_surface_scale", 1); + emuSettings.frame_rate_limit = + GetPrefInt(env, activity, "setting_frame_rate_limit", 60); + if (emuSettings.frame_rate_limit != 30 && + emuSettings.frame_rate_limit != 60) { + emuSettings.frame_rate_limit = 60; + } emuSettings.use_dsp = GetPrefBool(env, activity, "setting_use_dsp", false); emuSettings.hrtf = GetPrefBool(env, activity, "setting_hrtf", true); emuSettings.cache_shaders = GetPrefBool(env, activity, "setting_cache_shaders", true); diff --git a/android/app/src/main/cpp/xemu_settings_android.cc b/android/app/src/main/cpp/xemu_settings_android.cc index 8f02156fe6..5cbcdd442f 100644 --- a/android/app/src/main/cpp/xemu_settings_android.cc +++ b/android/app/src/main/cpp/xemu_settings_android.cc @@ -213,6 +213,7 @@ bool xemu_settings_load(void) setenv("XEMU_ANDROID_TCG_TUNING", "1", 1); setenv("XEMU_ANDROID_TCG_THREAD", "multi", 1); setenv("XEMU_ANDROID_TCG_TB_SIZE", "128", 1); + setenv("XEMU_ANDROID_TARGET_FPS", "60", 1); const char *path = xemu_settings_get_path(); if (!path || *path == '\0') { @@ -327,6 +328,16 @@ bool xemu_settings_load(void) tcg_thread->c_str()); } } + if (auto frame_rate_limit = + android_cfg["frame_rate_limit"].value()) { + int fps = (int)*frame_rate_limit; + if (fps != 30 && fps != 60) { + fps = 60; + } + char fps_str[16]; + snprintf(fps_str, sizeof(fps_str), "%d", fps); + setenv("XEMU_ANDROID_TARGET_FPS", fps_str, 1); + } if (auto tcg_tb_size = android_cfg["tcg_tb_size"].value()) { int tb_size = (int)*tcg_tb_size; if (tb_size < 32) { diff --git a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt index 7469a9e3b0..3c50d8071b 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt @@ -40,6 +40,7 @@ class SettingsActivity : AppCompatActivity() { val btn2x = findViewById(R.id.btn_scale_2x) val btn3x = findViewById(R.id.btn_scale_3x) val toggleDisplayMode = findViewById(R.id.toggle_display_mode) + val toggleFrameRate = findViewById(R.id.toggle_frame_rate) val toggleThread = findViewById(R.id.toggle_tcg_thread) val btnMulti = findViewById(R.id.btn_thread_multi) val btnSingle = findViewById(R.id.btn_thread_single) @@ -68,6 +69,12 @@ class SettingsActivity : AppCompatActivity() { else -> toggleDisplayMode.check(R.id.btn_display_stretch) } + val frameRateLimit = prefs.getInt("setting_frame_rate_limit", 60) + when (frameRateLimit) { + 30 -> toggleFrameRate.check(R.id.btn_fps_30) + else -> toggleFrameRate.check(R.id.btn_fps_60) + } + tvVulkanDriverName.text = prefs.getString("setting_vulkan_driver_name", null) ?: getString(R.string.settings_vulkan_driver_none) @@ -113,6 +120,11 @@ class SettingsActivity : AppCompatActivity() { R.id.btn_scale_3x -> 3 else -> 1 } + val selectedFrameRate = when (toggleFrameRate.checkedButtonId) { + R.id.btn_fps_30 -> 30 + R.id.btn_fps_60 -> 60 + else -> 60 + } val selectedThread = when (toggleThread.checkedButtonId) { R.id.btn_thread_single -> "single" else -> "multi" @@ -126,6 +138,7 @@ class SettingsActivity : AppCompatActivity() { val edit = prefs.edit() .putInt("setting_display_mode", selectedDisplayMode) .putInt("setting_surface_scale", selectedScale) + .putInt("setting_frame_rate_limit", selectedFrameRate) .putString("setting_tcg_thread", selectedThread) .putBoolean("setting_use_dsp", switchDsp.isChecked) .putBoolean("setting_hrtf", switchHrtf.isChecked) diff --git a/android/app/src/main/res/layout/activity_settings.xml b/android/app/src/main/res/layout/activity_settings.xml index 6dfff464e2..48b1f3210c 100644 --- a/android/app/src/main/res/layout/activity_settings.xml +++ b/android/app/src/main/res/layout/activity_settings.xml @@ -140,6 +140,40 @@ + + + + + + + + + + Stretch 4:3 16:9 + Frame Rate Limit + 30 FPS + 60 FPS Internal Resolution Scale 1x (Native 480p) 2x (960p) diff --git a/ui/xemu.c b/ui/xemu.c index cc4aae6676..b5ec1276d8 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -132,6 +132,8 @@ static bool g_android_use_hud = false; static bool g_android_paused = false; static bool g_android_should_quit = false; static uint64_t g_android_frame_counter = 0; +static int g_android_target_fps = 60; +static int64_t g_android_frame_interval_ns = 16666666; static GLuint g_android_blit_prog; static GLuint g_android_blit_vao; static GLuint g_android_blit_vbo; @@ -208,6 +210,29 @@ void xemu_android_set_display_mode_setting(int mode) g_android_display_mode = mode; } +static void xemu_android_refresh_frame_limit_from_env(void) +{ + int fps = 60; + const char *fps_env = SDL_getenv("XEMU_ANDROID_TARGET_FPS"); + if (fps_env && fps_env[0] != '\0') { + char *endptr = NULL; + long parsed = strtol(fps_env, &endptr, 10); + if (endptr != fps_env && (!endptr || *endptr == '\0')) { + fps = (int)parsed; + } + } + + if (fps != 30 && fps != 60) { + fps = 60; + } + + g_android_target_fps = fps; + g_android_frame_interval_ns = 1000000000LL / g_android_target_fps; + __android_log_print(ANDROID_LOG_INFO, "xemu-android", + "android: target FPS set to %d", + g_android_target_fps); +} + static bool android_blit_init(void) { if (g_android_blit_prog) { @@ -1377,6 +1402,7 @@ void xemu_android_display_loop(void) } } #ifdef __ANDROID__ + xemu_android_refresh_frame_limit_from_env(); SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0); if (g_android_use_hud) { xemu_hud_init(m_window, m_context); @@ -1825,10 +1851,14 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) qemu_mutex_unlock_main_loop(); /* - * Throttle to make sure swaps happen at 60Hz + * Throttle to keep swaps at the target frame rate. */ static int64_t last_update = 0; +#ifdef __ANDROID__ + int64_t deadline = last_update + g_android_frame_interval_ns; +#else int64_t deadline = last_update + 16666666; +#endif #ifdef DEBUG_XEMU_C int64_t sleep_acc = 0;