alittle better performance. depends on device.

This commit is contained in:
izzy2lost
2026-02-15 02:24:40 -05:00
parent 2b81d82def
commit 498eb794bc
5 changed files with 244 additions and 16 deletions
+84 -1
View File
@@ -11,10 +11,12 @@
#include <jni.h>
#include <climits>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <algorithm>
#include <string>
#include <vector>
#include <sys/stat.h>
@@ -205,6 +207,69 @@ static std::string JStringToString(JNIEnv* env, jstring value) {
return out;
}
static std::string ToLowerAscii(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return value;
}
static std::string GetBuildField(JNIEnv* env, const char* field_name) {
jclass buildClass = env->FindClass("android/os/Build");
if (!buildClass) {
HasException(env, "Build class lookup");
return {};
}
jfieldID field = env->GetStaticFieldID(buildClass, field_name, "Ljava/lang/String;");
if (!field) {
HasException(env, field_name);
env->DeleteLocalRef(buildClass);
return {};
}
jstring value =
static_cast<jstring>(env->GetStaticObjectField(buildClass, field));
if (HasException(env, field_name)) {
env->DeleteLocalRef(buildClass);
return {};
}
std::string out = JStringToString(env, value);
if (value) {
env->DeleteLocalRef(value);
}
env->DeleteLocalRef(buildClass);
return out;
}
static bool ShouldEnableInlineAioWorkaround() {
const char* forced = SDL_getenv("XEMU_ANDROID_INLINE_AIO");
if (forced) {
return forced[0] != '\0' && forced[0] != '0';
}
JNIEnv* env = GetEnv();
if (!env) {
return false;
}
const std::string device = ToLowerAscii(GetBuildField(env, "DEVICE"));
const std::string product = ToLowerAscii(GetBuildField(env, "PRODUCT"));
const std::string model = ToLowerAscii(GetBuildField(env, "MODEL"));
static const char* kAffectedDevices[] = {
"duchamp",
};
for (const char* marker : kAffectedDevices) {
if (device == marker ||
product.find(marker) != std::string::npos ||
model.find(marker) != std::string::npos) {
return true;
}
}
return false;
}
static std::string GetPrefString(JNIEnv* env, jobject activity, const char* key) {
jclass activityClass = env->GetObjectClass(activity);
jmethodID getPrefs = env->GetMethodID(activityClass, "getSharedPreferences",
@@ -324,10 +389,13 @@ static bool WriteConfigToml(const std::string& config_path,
toml::table* general = EnsureTable(tbl, "general");
toml::table* display = EnsureTable(tbl, "display");
toml::table* display_window = EnsureTable(*display, "window");
toml::table* audio = EnsureTable(tbl, "audio");
toml::table* audio_vp = EnsureTable(*audio, "vp");
toml::table* android = EnsureTable(tbl, "android");
toml::table* sys = EnsureTable(tbl, "sys");
toml::table* files = EnsureTable(*sys, "files");
if (!general || !display || !display_window || !android || !sys || !files) {
if (!general || !display || !display_window || !audio || !audio_vp ||
!android || !sys || !files) {
LogErrorFmt("Failed to build config tables at %s", config_path.c_str());
return false;
}
@@ -340,6 +408,15 @@ static bool WriteConfigToml(const std::string& config_path,
if (!display_window->contains("vsync")) {
display_window->insert_or_assign("vsync", false);
}
if (!audio_vp->contains("num_workers")) {
audio_vp->insert_or_assign("num_workers", 0);
}
if (!audio->contains("hrtf")) {
audio->insert_or_assign("hrtf", true);
}
if (!audio->contains("volume_limit")) {
audio->insert_or_assign("volume_limit", 1.0);
}
if (!android->contains("force_cpu_blit")) {
android->insert_or_assign("force_cpu_blit", false);
}
@@ -524,6 +601,12 @@ extern "C" int SDL_main(int argc, char* argv[]) {
SDL_GameControllerEventState(SDL_ENABLE);
LoadGameControllerMappingsFromAssets();
if (!SDL_getenv("XEMU_ANDROID_INLINE_AIO")) {
const bool use_inline_aio = ShouldEnableInlineAioWorkaround();
setenv("XEMU_ANDROID_INLINE_AIO", use_inline_aio ? "1" : "0", 1);
LogInfoFmt("XEMU_ANDROID_INLINE_AIO=%s", use_inline_aio ? "1" : "0");
}
SetupFiles setup = SyncSetupFiles();
if (!setup.config_path.empty()) {
@@ -196,6 +196,8 @@ const char *xemu_settings_get_default_eeprom_path(void)
bool xemu_settings_load(void)
{
const int kMaxAudioVpWorkers = 16;
xemu_settings_apply_defaults();
error_msg.clear();
setenv("XEMU_ANDROID_FORCE_CPU_BLIT", "0", 1);
@@ -219,6 +221,8 @@ bool xemu_settings_load(void)
auto general = tbl["general"];
auto display = tbl["display"];
auto display_window = display["window"];
auto audio = tbl["audio"];
auto audio_vp = audio["vp"];
auto perf = tbl["perf"];
auto android_cfg = tbl["android"];
auto sys = tbl["sys"];
@@ -259,6 +263,32 @@ bool xemu_settings_load(void)
g_config.perf.cache_shaders = *cache_shaders;
}
// Audio settings
if (auto vp_workers = audio_vp["num_workers"].value<int64_t>()) {
int workers = (int)*vp_workers;
if (workers < 0) {
workers = 0;
} else if (workers > kMaxAudioVpWorkers) {
workers = kMaxAudioVpWorkers;
}
g_config.audio.vp.num_workers = workers;
}
if (auto use_dsp = audio["use_dsp"].value<bool>()) {
g_config.audio.use_dsp = *use_dsp;
}
if (auto hrtf = audio["hrtf"].value<bool>()) {
g_config.audio.hrtf = *hrtf;
}
if (auto volume_limit = audio["volume_limit"].value<double>()) {
double volume = *volume_limit;
if (volume < 0.0) {
volume = 0.0;
} else if (volume > 1.0) {
volume = 1.0;
}
g_config.audio.volume_limit = volume;
}
// Android-specific settings
if (auto force_cpu_blit = android_cfg["force_cpu_blit"].value<bool>()) {
setenv("XEMU_ANDROID_FORCE_CPU_BLIT", *force_cpu_blit ? "1" : "0", 1);
@@ -291,6 +321,43 @@ bool xemu_settings_load(void)
snprintf(tb_size_str, sizeof(tb_size_str), "%d", tb_size);
setenv("XEMU_ANDROID_TCG_TB_SIZE", tb_size_str, 1);
}
if (auto inline_aio = android_cfg["inline_aio"].value<bool>()) {
setenv("XEMU_ANDROID_INLINE_AIO", *inline_aio ? "1" : "0", 1);
}
if (auto vp_workers = android_cfg["vp_workers"].value<int64_t>()) {
int workers = (int)*vp_workers;
if (workers < 0) {
workers = 0;
} else if (workers > kMaxAudioVpWorkers) {
workers = kMaxAudioVpWorkers;
}
char workers_str[16];
snprintf(workers_str, sizeof(workers_str), "%d", workers);
setenv("XEMU_ANDROID_VP_WORKERS", workers_str, 1);
}
if (auto audio_samples = android_cfg["audio_samples"].value<int64_t>()) {
int samples = (int)*audio_samples;
if (samples < 256) {
samples = 256;
} else if (samples > 4096) {
samples = 4096;
}
char samples_str[16];
snprintf(samples_str, sizeof(samples_str), "%d", samples);
setenv("XEMU_ANDROID_AUDIO_SAMPLES", samples_str, 1);
}
if (auto audio_fifo_frames =
android_cfg["audio_fifo_frames"].value<int64_t>()) {
int fifo_frames = (int)*audio_fifo_frames;
if (fifo_frames < 3) {
fifo_frames = 3;
} else if (fifo_frames > 32) {
fifo_frames = 32;
}
char fifo_str[16];
snprintf(fifo_str, sizeof(fifo_str), "%d", fifo_frames);
setenv("XEMU_ANDROID_AUDIO_FIFO_FRAMES", fifo_str, 1);
}
// System file paths
if (auto bootrom = sys_files["bootrom_path"].value<std::string>()) {
+2 -3
View File
@@ -2529,13 +2529,12 @@ static int coroutine_fn raw_thread_pool_submit(ThreadPoolFunc func, void *arg)
/*
* Some Android devices crash in coroutine context switches used by
* thread_pool_submit_co(). Allow forcing synchronous execution for
* raw I/O to avoid that path. Enabled by default on Android, set
* XEMU_ANDROID_INLINE_AIO=0 to restore thread-pool behavior.
* raw I/O to avoid that path when explicitly enabled.
*/
static int inline_aio = -1;
if (inline_aio < 0) {
const char *value = getenv("XEMU_ANDROID_INLINE_AIO");
inline_aio = (!value || value[0] != '0') ? 1 : 0;
inline_aio = (value && value[0] != '0') ? 1 : 0;
}
if (inline_aio) {
return func(arg);
+53 -11
View File
@@ -189,6 +189,29 @@ static void sleep_ns(int64_t ns)
#endif
}
static int getenv_int_clamped(const char *name, int min_value, int max_value,
int fallback)
{
const char *value = getenv(name);
if (!value || value[0] == '\0') {
return fallback;
}
char *end = NULL;
long parsed = strtol(value, &end, 10);
if (end == value || *end != '\0') {
return fallback;
}
if (parsed < min_value) {
return min_value;
}
if (parsed > max_value) {
return max_value;
}
return (int)parsed;
}
static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b)
{
MCPXAPUState *s = MCPX_APU_DEVICE(opaque);
@@ -199,29 +222,37 @@ static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b)
}
int avail = 0;
while (avail < free_b) {
for (int i = 0; i < 10; i++) {
qemu_spin_lock(&s->monitor.fifo_lock);
avail = fifo8_num_used(&s->monitor.fifo);
qemu_spin_unlock(&s->monitor.fifo_lock);
if (avail < free_b) {
sleep_ns(1000000);
qemu_cond_broadcast(&s->cond);
if (avail >= free_b) {
break;
}
sleep_ns(500000);
qemu_cond_broadcast(&s->cond);
if (!runstate_is_running()) {
memset(stream, 0, free_b);
return;
}
}
int copied = 0;
int to_copy = MIN(free_b, avail);
while (to_copy > 0) {
while (copied < to_copy) {
uint32_t chunk_len = 0;
qemu_spin_lock(&s->monitor.fifo_lock);
chunk_len = fifo8_pop_buf(&s->monitor.fifo, stream, to_copy);
assert(chunk_len <= to_copy);
chunk_len = fifo8_pop_buf(&s->monitor.fifo, stream + copied,
to_copy - copied);
qemu_spin_unlock(&s->monitor.fifo_lock);
stream += chunk_len;
to_copy -= chunk_len;
if (!chunk_len) {
break;
}
copied += chunk_len;
}
if (copied < free_b) {
memset(stream + copied, 0, free_b - copied);
}
qemu_cond_broadcast(&s->cond);
@@ -230,13 +261,24 @@ static void monitor_sink_cb(void *opaque, uint8_t *stream, int free_b)
static void monitor_init(MCPXAPUState *d)
{
qemu_spin_init(&d->monitor.fifo_lock);
fifo8_create(&d->monitor.fifo, 3 * (256 * 2 * 2));
int fifo_frames = 3;
int audio_samples = 512;
#ifdef __ANDROID__
fifo_frames = 8;
audio_samples = 1024;
fifo_frames = getenv_int_clamped("XEMU_ANDROID_AUDIO_FIFO_FRAMES", 3, 32,
fifo_frames);
audio_samples = getenv_int_clamped("XEMU_ANDROID_AUDIO_SAMPLES", 256, 4096,
audio_samples);
#endif
fifo8_create(&d->monitor.fifo, fifo_frames * sizeof(d->monitor.frame_buf));
struct SDL_AudioSpec sdl_audio_spec = {
.freq = 48000,
.format = AUDIO_S16LSB,
.channels = 2,
.samples = 512,
.samples = audio_samples,
.callback = monitor_sink_cb,
.userdata = d,
};
+38 -1
View File
@@ -1757,11 +1757,48 @@ voice_work_dispatch(MCPXAPUState *d,
qemu_mutex_unlock(&vwd->lock);
}
static int mcpx_apu_default_vp_worker_count(void)
{
int cpu_count = MAX(1, SDL_GetCPUCount());
#ifdef __ANDROID__
/*
* Mobile SoCs are often oversubscribed already (TCG + render + I/O).
* Keep VP worker defaults conservative to reduce thread contention and
* audio underruns; allow explicit override via env.
*/
const char *value = getenv("XEMU_ANDROID_VP_WORKERS");
if (value && value[0] != '\0') {
char *end = NULL;
long parsed = strtol(value, &end, 10);
if (end != value && *end == '\0' && parsed > 0) {
return (int)parsed;
}
}
if (cpu_count <= 2) {
return 1;
}
if (cpu_count <= 4) {
return 2;
}
if (cpu_count <= 6) {
return 3;
}
return 4;
#else
return cpu_count;
#endif
}
static void voice_work_init(MCPXAPUState *d)
{
VoiceWorkDispatch *vwd = &d->vp.voice_work_dispatch;
int num_workers = g_config.audio.vp.num_workers ?: SDL_GetCPUCount();
int num_workers = g_config.audio.vp.num_workers;
if (num_workers <= 0) {
num_workers = mcpx_apu_default_vp_worker_count();
}
vwd->num_workers = MAX(1, MIN(num_workers, MAX_VOICE_WORKERS));
vwd->workers = g_malloc0_n(vwd->num_workers, sizeof(VoiceWorker));
vwd->workers_should_exit = false;