diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4ff4e132ee..3f5cd42162 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -43,7 +43,11 @@ android { "-DXEMU_ANDROID_BUILD_ID=3", "-DXEMU_ENABLE_XISO_CONVERTER=ON", "-DCMAKE_C_FLAGS_DEBUG=-O2 -g0", - "-DCMAKE_CXX_FLAGS_DEBUG=-O2 -g0" + "-DCMAKE_CXX_FLAGS_DEBUG=-O2 -g0", + "-DCMAKE_C_FLAGS_RELEASE=-O3 -g0 -march=armv8.2-a -ffunction-sections -fdata-sections", + "-DCMAKE_CXX_FLAGS_RELEASE=-O3 -g0 -march=armv8.2-a -ffunction-sections -fdata-sections", + "-DCMAKE_EXE_LINKER_FLAGS_RELEASE=-Wl,--gc-sections", + "-DCMAKE_SHARED_LINKER_FLAGS_RELEASE=-Wl,--gc-sections" ) cppFlags += listOf("-std=c++17", "-fexceptions", "-frtti") } diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 9999514f54..8c70eb21a3 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -844,6 +844,7 @@ target_compile_definitions(xemu_core PRIVATE target_compile_options(xemu_core PRIVATE -g0 $<$,$,$>:-UNDEBUG> + $<$:-O3 -march=armv8.2-a -ffunction-sections -fdata-sections> ) target_include_directories(xemu_core PRIVATE @@ -913,6 +914,7 @@ endif() target_compile_options(xemu PRIVATE -fexceptions -frtti -g0 $<$,$,$>:-UNDEBUG> + $<$:-O3 -march=armv8.2-a -ffunction-sections -fdata-sections> ) target_include_directories(xemu PRIVATE diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c index 6db8de4c83..cb42be706b 100644 --- a/hw/xbox/nv2a/pgraph/vk/draw.c +++ b/hw/xbox/nv2a/pgraph/vk/draw.c @@ -22,6 +22,10 @@ #include "renderer.h" #include "hw/xbox/nv2a/pgraph/prim_rewrite.h" #include +#ifdef __ANDROID__ +#include +#include +#endif void pgraph_vk_draw_begin(NV2AState *d) { @@ -99,6 +103,22 @@ static bool pipeline_cache_entry_compare(Lru *lru, LruNode *node, return memcmp(&snode->key, key, sizeof(PipelineKey)); } +#ifdef __ANDROID__ +static char *get_pipeline_cache_path(PGRAPHVkState *r) +{ + char *pref_path = SDL_GetPrefPath("xemu", "xemu"); + if (!pref_path) { + return NULL; + } + char *path = g_strdup_printf("%svk_pipeline_cache_%08x_%08x.bin", + pref_path, + r->device_props.deviceID, + r->device_props.driverVersion); + SDL_free(pref_path); + return path; +} +#endif + static void init_pipeline_cache(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -110,9 +130,33 @@ static void init_pipeline_cache(PGRAPHState *pg) .pInitialData = NULL, .pNext = NULL, }; + +#ifdef __ANDROID__ + g_autofree char *cache_path = get_pipeline_cache_path(r); + gchar *cache_data = NULL; + gsize cache_data_size = 0; + if (cache_path) { + GError *err = NULL; + if (g_file_get_contents(cache_path, &cache_data, &cache_data_size, &err)) { + cache_info.initialDataSize = (size_t)cache_data_size; + cache_info.pInitialData = (const void *)cache_data; + __android_log_print(ANDROID_LOG_INFO, "xemu-vk", + "Loaded pipeline cache: %zu bytes", cache_data_size); + } else { + if (err) { + g_error_free(err); + } + } + } +#endif + VK_CHECK(vkCreatePipelineCache(r->device, &cache_info, NULL, &r->vk_pipeline_cache)); +#ifdef __ANDROID__ + g_free(cache_data); +#endif + const size_t pipeline_cache_size = 2048; lru_init(&r->pipeline_cache); r->pipeline_cache_entries = @@ -135,6 +179,34 @@ static void finalize_pipeline_cache(PGRAPHState *pg) g_free(r->pipeline_cache_entries); r->pipeline_cache_entries = NULL; +#ifdef __ANDROID__ + g_autofree char *cache_path = get_pipeline_cache_path(r); + if (cache_path) { + size_t data_size = 0; + VkResult res = vkGetPipelineCacheData(r->device, r->vk_pipeline_cache, + &data_size, NULL); + if (res == VK_SUCCESS && data_size > 0) { + g_autofree void *data = g_malloc(data_size); + res = vkGetPipelineCacheData(r->device, r->vk_pipeline_cache, + &data_size, data); + if (res == VK_SUCCESS) { + GError *err = NULL; + if (g_file_set_contents(cache_path, (const gchar *)data, + (gssize)data_size, &err)) { + __android_log_print(ANDROID_LOG_INFO, "xemu-vk", + "Saved pipeline cache: %zu bytes", data_size); + } else { + __android_log_print(ANDROID_LOG_WARN, "xemu-vk", + "Failed to save pipeline cache"); + if (err) { + g_error_free(err); + } + } + } + } + } +#endif + vkDestroyPipelineCache(r->device, r->vk_pipeline_cache, NULL); } diff --git a/hw/xbox/nv2a/pgraph/vk/surface.c b/hw/xbox/nv2a/pgraph/vk/surface.c index f10e78c55f..915e7d0cbd 100644 --- a/hw/xbox/nv2a/pgraph/vk/surface.c +++ b/hw/xbox/nv2a/pgraph/vk/surface.c @@ -498,15 +498,14 @@ void pgraph_vk_wait_for_surface_download(SurfaceBinding *surface) NV2AState *d = g_nv2a; bool require_download = qatomic_read(&surface->draw_dirty); -#ifdef __ANDROID__ /* - * Android Vulkan currently presents through the CPU/VGA fallback path. - * Force framebuffer surface download so fallback upload sees fresh pixels. + * Android presents through the CPU/VGA fallback path (no external memory + * interop). We rely on draw_dirty to gate downloads: it is set atomically + * whenever the Vulkan renderer draws to this surface and cleared after each + * successful download. Skipping the download when draw_dirty is false + * avoids a GPU stall on frames where the Xbox GPU has not redrawn the + * framebuffer (static screens, menus, load screens, etc.). */ - if (!d->pgraph.vk_renderer_state->display.use_external_memory) { - require_download = true; - } -#endif if (require_download) { qemu_mutex_lock(&d->pfifo.lock); diff --git a/ui/xemu.c b/ui/xemu.c index d94ef1ce77..2ad936f2b3 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -1381,7 +1381,7 @@ void xemu_android_display_loop(void) sdl2_poll_events(&sdl2_console[0]); bql_unlock(); qemu_mutex_unlock_main_loop(); - SDL_Delay(16); + SDL_Delay(100); continue; } sdl2_gl_refresh(&sdl2_console[0].dcl); @@ -1570,7 +1570,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) sdl2_poll_events(scon); bql_unlock(); qemu_mutex_unlock_main_loop(); - SDL_Delay(16); + SDL_Delay(100); return; } #endif @@ -1776,7 +1776,11 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) bql_unlock(); qemu_mutex_unlock_main_loop(); +#ifdef __ANDROID__ + glFlush(); +#else glFinish(); +#endif nv2a_release_framebuffer_surface(); #ifdef __ANDROID__ android_log_gl_error("refresh-finish"); @@ -1807,7 +1811,9 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) int64_t spin_acc = 0; #endif -#ifndef _WIN32 +#ifdef __ANDROID__ + const int64_t sleep_threshold = 500000; // 0.5ms — Android CFS scheduler jitter is ~0.2ms +#elif !defined(_WIN32) const int64_t sleep_threshold = 2000000; #else const int64_t sleep_threshold = 250000;