diff --git a/android/app/src/main/cpp/xemu_android.cpp b/android/app/src/main/cpp/xemu_android.cpp index 055e89b10b..81a3f96e17 100644 --- a/android/app/src/main/cpp/xemu_android.cpp +++ b/android/app/src/main/cpp/xemu_android.cpp @@ -201,14 +201,9 @@ static std::string ToLowerAscii(std::string value) { static std::string ResolveAndroidAudioDriverHint() { // OpenSL ES is preferred over AAudio because AAudio exclusively requests // MMAP no-IRQ low-latency outputs (AUDIO_OUTPUT_FLAG_MMAP_NOIRQ). On some - // devices (e.g. Honor/Huawei with Android 14+, Snapdragon 8 Gen 3/Adreno 840) - // the MMAP output count is capped and openDirectOutput fails when the limit - // is reached, leaving the audio stream permanently inactive (isActive:0) and - // hanging the audio thread with 1ms timeouts in AudioStreamInternal_Client. - // AAudio is deliberately excluded from the default chain: if OpenSL ES fails - // for any reason, "android" (standard AudioTrack) is the next safe fallback. - // "dummy" is the silent no-op driver used as a last resort so that audio - // failure never prevents the emulator from starting. + // devices the MMAP output count is capped and openDirectOutput fails when + // the limit is reached, leaving the audio stream inactive. The standard + // Android AudioTrack backend remains the safe fallback. constexpr const char* kDefaultAudioDriverHint = "openslES,android,dummy"; const char* value = SDL_getenv("XEMU_ANDROID_AUDIO_DRIVER"); if (!value || value[0] == '\0') { @@ -227,7 +222,10 @@ static std::string ResolveAndroidAudioDriverHint() { return "aaudio,android,dummy"; } if (normalized == "android" || normalized == "audiotrack") { - return "android,dummy"; + // Legacy recovery: a saved "android" preference should no longer force + // AudioTrack first, because that path was found to exit immediately on + // some devices. Fall back to the old OpenSL ES-first behavior. + return "openslES,android,dummy"; } if (normalized == "null" || normalized == "none" || normalized == "dummy") { return "dummy"; @@ -723,7 +721,7 @@ struct EmulatorSettings { bool hrtf = true; bool cache_shaders = true; bool hard_fpu = true; - bool vsync = false; + bool vsync = true; bool skip_boot_anim = false; }; @@ -983,8 +981,14 @@ static SetupFiles SyncSetupFiles() { emuSettings.filtering = "nearest"; } } - emuSettings.vsync = GetPrefBool(env, activity, "setting_vsync", false); + emuSettings.vsync = GetPrefBool(env, activity, "setting_vsync", true); out.audio_driver = GetPrefString(env, activity, "setting_audio_driver"); + { + std::string normalized = ToLowerAscii(out.audio_driver); + if (normalized == "android" || normalized == "audiotrack") { + out.audio_driver = "openslES"; + } + } int displayMode = GetPrefInt(env, activity, "setting_display_mode", 0); xemu_android_set_display_mode_setting(displayMode); diff --git a/android/app/src/main/cpp/xemu_settings_android.cc b/android/app/src/main/cpp/xemu_settings_android.cc index 6a0eae164d..4c1c05d730 100644 --- a/android/app/src/main/cpp/xemu_settings_android.cc +++ b/android/app/src/main/cpp/xemu_settings_android.cc @@ -76,7 +76,7 @@ static void xemu_settings_apply_defaults(void) CONFIG_DISPLAY_WINDOW_STARTUP_SIZE_1280X960; g_config.display.window.last_width = 640; g_config.display.window.last_height = 480; - g_config.display.window.vsync = false; + g_config.display.window.vsync = true; g_config.display.ui.show_menubar = true; g_config.display.ui.show_notifications = true; g_config.display.ui.hide_cursor = true; @@ -392,8 +392,8 @@ bool xemu_settings_load(void) if (auto audio_driver = android_cfg["audio_driver"].value()) { std::string driver = *audio_driver; std::string normalized = to_lower_ascii(driver); - if (normalized == "audiotrack") { - setenv("XEMU_ANDROID_AUDIO_DRIVER", "android", 1); + if (normalized == "audiotrack" || normalized == "android") { + setenv("XEMU_ANDROID_AUDIO_DRIVER", "opensles", 1); } else if (normalized == "opensl" || normalized == "opensles") { setenv("XEMU_ANDROID_AUDIO_DRIVER", "opensles", 1); } else if (normalized == "auto" || normalized == "default") { diff --git a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt index 16e9b43332..72b44dacf2 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt @@ -7,12 +7,14 @@ import android.hardware.input.InputManager import android.net.Uri import android.os.Build import android.os.Bundle +import android.util.Log import android.view.InputDevice import android.view.KeyEvent import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowInsetsController +import android.view.WindowManager import android.widget.BaseAdapter import android.widget.FrameLayout import android.widget.ImageView @@ -33,6 +35,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { const val EXTRA_AUTO_LOAD_SNAPSHOT_SLOT = "com.izzy2lost.x1box.extra.AUTO_LOAD_SNAPSHOT_SLOT" private const val SNAPSHOT_PREVIEW_HEADER_SIZE = 12 private const val TOTAL_SNAPSHOT_SLOTS = 10 + private const val TAG = "MainActivity" } private data class SnapshotSlotPreview( @@ -60,11 +63,13 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) + keepScreenOn = true } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) val requestedSlot = intent?.getIntExtra(EXTRA_AUTO_LOAD_SNAPSHOT_SLOT, 0) ?: 0 if (requestedSlot in 1..TOTAL_SNAPSHOT_SLOTS) { startupSnapshotSlot = requestedSlot @@ -155,6 +160,8 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { override fun onResume() { super.onResume() + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + mLayout?.keepScreenOn = true // Register virtual controller after SDL is initialized // Use a delay to ensure SDL is fully ready @@ -272,6 +279,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { } override fun onDestroy() { + Log.i(TAG, "onDestroy()") inGameMenuDialog?.dismiss() inGameMenuDialog = null @@ -283,9 +291,25 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { } inputManager?.unregisterInputDeviceListener(this) + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) super.onDestroy() } + override fun onUserLeaveHint() { + Log.i(TAG, "onUserLeaveHint()") + super.onUserLeaveHint() + } + + override fun onTrimMemory(level: Int) { + Log.i(TAG, "onTrimMemory(level=$level)") + super.onTrimMemory(level) + } + + override fun onLowMemory() { + Log.w(TAG, "onLowMemory()") + super.onLowMemory() + } + // Manual control methods (for settings/preferences) fun toggleOnScreenController() { isControllerVisible = !isControllerVisible 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 d6ab784179..a6c13baebd 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt @@ -166,7 +166,7 @@ class SettingsActivity : AppCompatActivity() { switchHrtf.isChecked = prefs.getBoolean("setting_hrtf", true) switchShaders.isChecked = prefs.getBoolean("setting_cache_shaders", true) switchFpu.isChecked = prefs.getBoolean("setting_hard_fpu", true) - switchVsync.isChecked = prefs.getBoolean("setting_vsync", false) + switchVsync.isChecked = prefs.getBoolean("setting_vsync", true) switchSkipBootAnim.isChecked = prefs.getBoolean("setting_skip_boot_anim", false) diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.c b/hw/xbox/nv2a/pgraph/glsl/geom.c index 5bb4ffab88..64e957b654 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.c +++ b/hw/xbox/nv2a/pgraph/glsl/geom.c @@ -67,6 +67,7 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) bool need_triz = false; bool need_linez = false; + bool need_point_size = false; const char *layout_in = NULL; const char *layout_out = NULL; const char *body = NULL; @@ -99,6 +100,7 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) " emit_line(2, 0, dz);\n"; } else { assert(polygon_mode == POLY_MODE_POINT); + need_point_size = true; layout_out = "layout(points, max_vertices = 3) out;\n"; body = " mat4 pz = calc_triz(0, 1, 2);\n" " emit_vertex(0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" @@ -134,11 +136,14 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) const char *point_size_expr = opts.gles ? "v_vtxPointSize[index]" : "gl_in[index].gl_PointSize"; + mstring_append(output, + "void emit_vertex(int index, mat4 pz) {\n" + " gl_Position = gl_in[index].gl_Position;\n"); + if (need_point_size) { + mstring_append_fmt(output, " gl_PointSize = %s;\n", point_size_expr); + } mstring_append_fmt( output, - "void emit_vertex(int index, mat4 pz) {\n" - " gl_Position = gl_in[index].gl_Position;\n" - " gl_PointSize = %s;\n" " vtxD0 = v_vtxD0[%s];\n" " vtxD1 = v_vtxD1[%s];\n" " vtxB0 = v_vtxB0[%s];\n" @@ -155,7 +160,6 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) " vtxPointSize = v_vtxPointSize[index];\n" " EmitVertex();\n" "}\n", - point_size_expr, provoking_index, provoking_index, provoking_index, diff --git a/ui/xemu.c b/ui/xemu.c index ca8d106fc7..769fb0416a 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -128,6 +128,7 @@ static void toggle_full_screen(struct sdl2_console *scon); #ifdef __ANDROID__ static bool g_android_gl_bgra_supported = true; +static bool g_android_force_finish_before_swap = false; static bool g_android_paused = false; static bool g_android_should_quit = false; static uint64_t g_android_frame_counter = 0; @@ -218,6 +219,8 @@ static void sdl2_gl_render_texture(struct sdl2_console *scon, { int w = 0; int h = 0; + int ww = 0; + int wh = 0; int vx = 0; int vy = 0; int vw; @@ -232,6 +235,7 @@ static void sdl2_gl_render_texture(struct sdl2_console *scon, android_log_gl_error("blit-start"); SDL_GL_GetDrawableSize(scon->real_window, &w, &h); + SDL_GetWindowSize(scon->real_window, &ww, &wh); if (w <= 0) { w = 1; } @@ -255,6 +259,15 @@ static void sdl2_gl_render_texture(struct sdl2_console *scon, } } +#ifdef __ANDROID__ + if ((g_android_frame_counter % 60) == 0) { + __android_log_print(ANDROID_LOG_INFO, "xemu-android", + "present drawable=%dx%d window=%dx%d viewport=%d,%d %dx%d tex=%u flip=%d mode=%d", + w, h, ww, wh, vx, vy, vw, vh, + (unsigned)tex, flip ? 1 : 0, g_android_display_mode); + } +#endif + glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(vx, vy, vw, vh); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); @@ -969,6 +982,7 @@ static void sdl2_display_very_early_init(DisplayOptions *o) int min_window_height = 480; int window_width = min_window_width; int window_height = min_window_height; + SDL_DisplayMode disp_mode; const int res_table[][2] = { {640, 480}, @@ -998,6 +1012,22 @@ static void sdl2_display_very_early_init(DisplayOptions *o) window_height = min_window_height; } +#ifdef __ANDROID__ + /* Android should always present into the full activity surface. Using the + * desktop-style startup window size here lets narrow-height devices fall + * into the "display smaller than requested window" path and clamps the SDL + * window to 640x480, which is exactly the bottom-left tiny box symptom. + */ + if (SDL_GetCurrentDisplayMode(0, &disp_mode) == 0) { + if (disp_mode.w > 0) { + window_width = disp_mode.w; + } + if (disp_mode.h > 0) { + window_height = disp_mode.h; + } + } +#endif + // On Android, always use OpenGL window even for Vulkan because Vulkan // needs GL context for external memory display presentation #ifdef __ANDROID__ @@ -1021,12 +1051,13 @@ static void sdl2_display_very_early_init(DisplayOptions *o) g_free(title); SDL_SetWindowMinimumSize(m_window, min_window_width, min_window_height); - SDL_DisplayMode disp_mode; SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(m_window), &disp_mode); +#ifndef __ANDROID__ if (disp_mode.w < window_width || disp_mode.h < window_height) { SDL_SetWindowSize(m_window, min_window_width, min_window_height); SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); } +#endif m_context = SDL_GL_CreateContext(m_window); @@ -1111,6 +1142,12 @@ static void sdl2_display_very_early_init(DisplayOptions *o) __android_log_print(ANDROID_LOG_INFO, "xemu-android", "GL_EXT_texture_format_BGRA8888=%s", g_android_gl_bgra_supported ? "yes" : "no"); + if ((vendor && strstr(vendor, "ARM")) || + (renderer && strstr(renderer, "Mali"))) { + g_android_force_finish_before_swap = true; + __android_log_print(ANDROID_LOG_INFO, "xemu-android", + "android: force glFinish before swap enabled"); + } } #endif @@ -1318,7 +1355,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); + SDL_GL_SetSwapInterval(1); xemu_hud_init(m_window, m_context); #endif tcg_register_init_ctx(); @@ -1641,7 +1678,11 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) qemu_mutex_unlock_main_loop(); #ifdef __ANDROID__ - glFlush(); + if (g_android_force_finish_before_swap) { + glFinish(); + } else { + glFlush(); + } #else glFinish(); #endif