mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
alittle better performance. depends on device.
This commit is contained in:
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user