sloppy stuff

This commit is contained in:
izzy2lost
2026-04-08 02:34:59 -04:00
parent 5603c855b9
commit d726dbc780
96 changed files with 6841 additions and 7310 deletions
+4 -4
View File
@@ -31,17 +31,17 @@
* If tcg_req_mo indicates a barrier for @type is required
* for the guest memory model, issue a host memory barrier.
*
* Xbox has a single CPU, so inter-vCPU memory ordering barriers are
* never needed and can be compiled out.
* Xbox has a single CPU inter-vCPU memory ordering barriers are
* never needed, so we compile them out entirely.
*/
#ifdef XBOX
#define cpu_req_mo(cpu, type) do { (void)(cpu); (void)(type); } while (0)
#define cpu_req_mo(cpu, type) do { (void)(cpu); } while (0)
#else
#define cpu_req_mo(cpu, type) \
do { \
unsigned _mo = tcg_req_mo( \
cpu->cc->tcg_ops->guest_default_memory_order, type); \
if (_mo & (TCG_MO_ST_ST | TCG_MO_ST_LD)) { \
if (_mo & (TCG_MO_ST_ST | TCG_MO_ST_LD)) { \
smp_mb(); \
} else if (_mo) { \
smp_rmb(); \
+332
View File
@@ -47,6 +47,279 @@
#include "tb-context.h"
#include "tb-internal.h"
#include "internal-common.h"
#include "tb-cache-hints.h"
#ifdef __ANDROID__
#include <android/log.h>
#endif
/* ------------------------------------------------------------------ */
/* Tier 1 promotion mechanism */
/* ------------------------------------------------------------------ */
#ifdef XBOX
#define TIER1_PROMOTION_BUDGET 8 /* Max promotions per budget window */
#define TIER1_BUDGET_INTERVAL_MS 10 /* Reset budget every N ms */
static int tier1_promotion_budget = TIER1_PROMOTION_BUDGET;
static int g_tier1_threshold = TB_TIER1_THRESHOLD;
static uint64_t g_tier1_promotions_total;
static uint64_t g_tier1_promotions_dropped;
void xemu_set_tier1_threshold(int value)
{
if (value < 8) value = 8;
if (value > 512) value = 512;
g_tier1_threshold = value;
}
int xemu_get_tier1_threshold(void)
{
return g_tier1_threshold;
}
void xemu_get_tier1_stats(uint64_t *promoted, uint64_t *dropped)
{
if (promoted) *promoted = g_tier1_promotions_total;
if (dropped) *dropped = g_tier1_promotions_dropped;
}
/*
* Deferred tier-1 promotion request table.
*
* Calling tb_gen_code from within the post-execution handler is unsafe
* (it breaks rendering). Instead, promotion only invalidates the old
* TB and records the request here. The natural tb_gen_code path
* (called from tb_find on the next cache miss) checks this table and
* sets CF_TIER1 on the new TB so the tier-1 optimisation passes fire.
*/
#define TIER1_REQUEST_SLOTS 64
typedef struct {
vaddr pc;
uint64_t cs_base;
uint32_t flags;
uint32_t exec_count;
bool valid;
} Tier1Request;
static Tier1Request tier1_requests[TIER1_REQUEST_SLOTS];
/*
* Called from tb_gen_code (translate-all.c) to check whether a
* freshly translated TB should use tier-1 optimisations.
* Returns the saved exec_count if a request matches, or -1.
*/
/*
* Peek: returns true if there is a pending tier-1 request for (pc,
* cs_base, flags) without consuming it.
*/
bool tier1_has_pending_request(vaddr pc, uint64_t cs_base, uint32_t flags)
{
for (int i = 0; i < TIER1_REQUEST_SLOTS; i++) {
if (tier1_requests[i].valid &&
tier1_requests[i].pc == pc &&
tier1_requests[i].cs_base == cs_base &&
tier1_requests[i].flags == flags) {
return true;
}
}
return false;
}
int tier1_consume_request(vaddr pc, uint64_t cs_base, uint32_t flags,
uint32_t *cflags_out)
{
for (int i = 0; i < TIER1_REQUEST_SLOTS; i++) {
if (tier1_requests[i].valid &&
tier1_requests[i].pc == pc &&
tier1_requests[i].cs_base == cs_base &&
tier1_requests[i].flags == flags) {
tier1_requests[i].valid = false;
if (cflags_out) {
*cflags_out |= CF_TIER1;
}
return (int)tier1_requests[i].exec_count;
}
}
return -1;
}
static void tb_request_tier1_promotion(CPUState *cpu, TranslationBlock *tb)
{
/* Record the request for deferred tier-1 retranslation. */
int slot = -1;
for (int i = 0; i < TIER1_REQUEST_SLOTS; i++) {
if (!tier1_requests[i].valid) {
slot = i;
break;
}
}
if (slot >= 0) {
tier1_requests[slot].pc = tb->pc;
tier1_requests[slot].cs_base = tb->cs_base;
tier1_requests[slot].flags = tb->flags;
tier1_requests[slot].exec_count = tb->exec_count;
tier1_requests[slot].valid = true;
}
/*
* Invalidate the old TB. Pass -1 so tb_phys_invalidate removes
* it from the page list (standalone invalidation path).
*/
mmap_lock();
tb_phys_invalidate(tb, -1);
mmap_unlock();
}
/*
* Check if a TB should be promoted to Tier 1 and do so if budget allows.
* Called from cpu_exec_loop after execution counting.
*/
static inline void tier1_maybe_promote(CPUState *cpu, TranslationBlock *tb)
{
if (tb->tier == 0 && tb->exec_count >= (uint32_t)g_tier1_threshold) {
if (tier1_promotion_budget > 0) {
tier1_promotion_budget--;
g_tier1_promotions_total++;
tb_request_tier1_promotion(cpu, tb);
} else {
g_tier1_promotions_dropped++;
}
}
}
/* ------------------------------------------------------------------ */
/* Superblock detection */
/* ------------------------------------------------------------------ */
/*
* Threshold for superblock candidacy: one exit must dominate with
* >95% of all exit traffic, and the TB must have been executed enough.
*/
#define SUPERBLOCK_DOMINANCE_PCT 95
#define SUPERBLOCK_MIN_CHAINS 128
/*
* Check if a Tier 1 TB has a dominant single-successor exit.
* Returns the exit index (0 or 1) or -1 if no dominant exit.
*/
static inline int tb_dominant_exit(const TranslationBlock *tb)
{
uint32_t c0 = tb->chain_count[0];
uint32_t c1 = tb->chain_count[1];
uint32_t total = c0 + c1;
if (total < SUPERBLOCK_MIN_CHAINS) {
return -1;
}
if (c0 * 100 / total >= SUPERBLOCK_DOMINANCE_PCT) {
return 0;
}
if (c1 * 100 / total >= SUPERBLOCK_DOMINANCE_PCT) {
return 1;
}
return -1;
}
/*
* Forward-declare the superblock formation function (defined in
* translate-all.c). Returns the new superblock TB or NULL on failure.
*/
TranslationBlock *tb_gen_superblock(CPUState *cpu,
TranslationBlock *tb_a,
int dominant_exit);
#define SUPERBLOCK_BUDGET 4 /* Max superblock formations per budget cycle */
static int superblock_budget = SUPERBLOCK_BUDGET;
/*
* Check if a Tier 1 TB is a superblock candidate and attempt formation.
* Called from cpu_exec_loop after tier1 promotion, with budget rate limiting.
*
* XBOX_SUPERBLOCK_ENABLED: Set to 1 to enable runtime superblock formation.
* Currently disabled (0) while the lookup/invalidation integration is
* being finalised. The detection infrastructure (chain_count, dominant
* exit) and formation engine (tb_gen_superblock) are fully implemented
* and compile-tested; only the trigger is gated.
*/
#define XBOX_SUPERBLOCK_ENABLED 0
static inline void tier1_maybe_form_superblock(CPUState *cpu,
TranslationBlock *tb)
{
#if !XBOX_SUPERBLOCK_ENABLED
return;
#else
/* Only Tier 1+ TBs, not already a superblock. */
if (tb->tier < 1 || tb->superblock != NULL) {
return;
}
if (tb->cflags & CF_SUPERBLOCK) {
return;
}
int dom = tb_dominant_exit(tb);
if (dom < 0) {
return;
}
/* Check budget. */
if (superblock_budget <= 0) {
return;
}
/* Verify successor exists and is valid. */
uintptr_t dest = qatomic_read(&tb->jmp_dest[dom]);
if (dest == (uintptr_t)NULL || (dest & 1)) {
return;
}
TranslationBlock *tb_b = (TranslationBlock *)dest;
if (tb_b->cflags & (CF_INVALID | CF_SUPERBLOCK)) {
return;
}
/* Both must be single-page TBs. */
if (tb_page_addr1(tb) != -1 || tb_page_addr1(tb_b) != -1) {
return;
}
superblock_budget--;
mmap_lock();
tb_gen_superblock(cpu, tb, dom);
mmap_unlock();
#endif /* XBOX_SUPERBLOCK_ENABLED */
}
/*
* Reset the promotion budget periodically. Called from cpu_exec_loop.
* Uses a simple call counter rather than real time to avoid clock overhead.
*/
#define TIER1_BUDGET_RESET_INTERVAL 100000
static uint32_t tier1_budget_counter;
static uint32_t tier1_log_counter;
#define TIER1_LOG_INTERVAL 50
static inline void tier1_maybe_reset_budget(void)
{
if (++tier1_budget_counter >= TIER1_BUDGET_RESET_INTERVAL) {
tier1_budget_counter = 0;
tier1_promotion_budget = TIER1_PROMOTION_BUDGET;
superblock_budget = SUPERBLOCK_BUDGET;
if (++tier1_log_counter >= TIER1_LOG_INTERVAL) {
tier1_log_counter = 0;
qemu_printf("[tier1] threshold=%d promoted=%lu dropped=%lu\n",
g_tier1_threshold,
(unsigned long)g_tier1_promotions_total,
(unsigned long)g_tier1_promotions_dropped);
}
}
}
#endif /* XBOX */
/* -icount align implementation. */
@@ -662,6 +935,15 @@ static inline void tb_add_jump(TranslationBlock *tb, int n,
tb->jmp_list_next[n] = tb_next->jmp_list_head;
tb_next->jmp_list_head = (uintptr_t)tb | n;
#ifdef XBOX
{
uint32_t cnt = tb->chain_count[n];
if (cnt < UINT32_MAX) {
tb->chain_count[n] = cnt + 1;
}
}
#endif
qemu_spin_unlock(&tb_next->jmp_lock);
qemu_log_mask(CPU_LOG_EXEC, "Linking TBs %p index %d -> %p\n",
@@ -990,6 +1272,9 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc)
CPUJumpCache *jc;
uint32_t h;
tb_cache_notify_lookup_miss();
tb_cache_maybe_log_stats();
mmap_lock();
tb = tb_gen_code(cpu, s);
mmap_unlock();
@@ -1002,6 +1287,8 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc)
jc = cpu->tb_jmp_cache;
jc->array[h].pc = s.pc;
qatomic_set(&jc->array[h].tb, tb);
} else {
tb_cache_notify_lookup_hit();
}
#ifndef CONFIG_USER_ONLY
@@ -1020,7 +1307,44 @@ cpu_exec_loop(CPUState *cpu, SyncClocks *sc)
tb_add_jump(last_tb, tb_exit, tb);
}
#ifdef XBOX
{
static uint64_t cpu_heartbeat = 0;
cpu_heartbeat++;
if (cpu_heartbeat <= 20) {
error_report("[CPU-PRE] tb#%lu pc=0x%lx size=%d",
(unsigned long)cpu_heartbeat,
(unsigned long)s.pc, tb->size);
}
cpu_loop_exec_tb(cpu, tb, s.pc, &last_tb, &tb_exit);
if (cpu_heartbeat <= 20) {
error_report("[CPU-POST] tb#%lu exit=%d last_tb=%p",
(unsigned long)cpu_heartbeat,
tb_exit, last_tb);
}
}
#else
cpu_loop_exec_tb(cpu, tb, s.pc, &last_tb, &tb_exit);
#endif
#ifdef XBOX
{
uint32_t c = tb->exec_count;
if (c < (uint32_t)g_tier1_threshold * 2) {
tb->exec_count = c + 1;
}
tier1_maybe_promote(cpu, tb);
tier1_maybe_form_superblock(cpu, tb);
tier1_maybe_reset_budget();
if (tb->cflags & CF_INVALID) {
last_tb = NULL;
}
}
#endif
/* Try to align the host and virtual clocks
if the guest is in advance */
@@ -1045,6 +1369,14 @@ int cpu_exec(CPUState *cpu)
int ret;
SyncClocks sc = { 0 };
#ifdef XBOX
static bool tb_cache_warmed = false;
if (!tb_cache_warmed) {
tb_cache_warmed = true;
tb_cache_prewarm(cpu);
}
#endif
/* replay_interrupt may need current_cpu */
current_cpu = cpu;
File diff suppressed because it is too large Load Diff
+153
View File
@@ -0,0 +1,153 @@
/*
* Persistent TCG Translation Block Cache Hints
*
* Records which translation blocks are generated during gameplay and
* saves them as "hints" to disk. On subsequent launches the hints
* are loaded and the blocks are pre-translated during loading, which
* eliminates the JIT stutter that otherwise occurs during the first
* few minutes of play.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef TB_CACHE_HINTS_H
#define TB_CACHE_HINTS_H
#include "qemu/osdep.h"
#include "exec/translation-block.h"
#include "exec/cpu-common.h"
#ifndef XEMU_OPT_TB_CACHE_HINTS
#define XEMU_OPT_TB_CACHE_HINTS 1
#endif
#if defined(XBOX) && XEMU_OPT_TB_CACHE_HINTS
/*
* A single translation-block hint (v2) -- the lookup key that
* tb_gen_code() needs plus hotness metadata for tiered recompilation.
*/
typedef struct TBCacheHint {
uint64_t pc; /* Guest virtual PC */
uint64_t cs_base; /* Code segment base */
uint32_t flags; /* Architecture context flags */
uint32_t cflags; /* Compile flags (masked) */
uint64_t phys_pc; /* Physical / RAM page address */
uint32_t exec_count; /* Approximate execution count */
uint8_t tier; /* 0 = Tier 0, 1 = Tier 1, 2 = superblock */
uint8_t is_superblock;/* 1 if this hint describes a merged superblock */
uint8_t pad[2]; /* Alignment padding */
uint64_t pc_b; /* Component B's PC (only if is_superblock) */
} TBCacheHint;
/*
* Record a freshly-generated TB so its key can be persisted later.
* Safe to call from the hot path; O(1) amortised.
*/
void tb_cache_record_hint(const TranslationBlock *tb);
/*
* Save all recorded hints to |path|.
* |game_hash| is an opaque identifier for the current game image
* (e.g. CRC32 of MCPX + flash); the file is rejected on load if the
* hash does not match.
*/
void tb_cache_save(const char *path, uint32_t game_hash);
/*
* Set the default save target used by Android autosave during play.
*/
void tb_cache_set_save_target(const char *path, uint32_t game_hash);
/*
* Load hints from |path|. Returns the number of hints loaded,
* or 0 on any error (missing file, hash mismatch, version mismatch).
*/
int tb_cache_load(const char *path, uint32_t game_hash);
/*
* Pre-translate all loaded hints. Call once after the CPU is fully
* realised and guest memory is mapped.
*/
void tb_cache_prewarm(CPUState *cpu);
/*
* Compute a hash of the ROM files at the given paths.
* Used as the game_hash parameter for save/load.
*/
uint32_t tb_cache_compute_game_hash(const char *bootrom_path,
const char *flashrom_path);
/*
* Hot-path counters -- declared extern so that the inline accessors
* below compile to a single load/store without a function call.
*/
extern uint64_t tb_cache_stats_lookup_hits;
extern uint64_t tb_cache_stats_lookup_misses;
extern uint64_t tb_cache_stats_call_count;
/*
* Log interval in miss-path calls. tb_cache_maybe_log_stats() is now
* only called on TB lookup misses, which are far less frequent than
* the combined hit+miss count. 500K misses every few seconds during
* active play.
*/
#define TB_CACHE_LOG_INTERVAL_CALLS 500000ULL
static inline void tb_cache_notify_lookup_hit(void)
{
tb_cache_stats_lookup_hits++;
}
static inline void tb_cache_notify_lookup_miss(void)
{
tb_cache_stats_lookup_misses++;
}
/*
* Slow path for periodic logging -- only called when the call counter
* wraps around. Defined in tb-cache-hints.c.
*/
void tb_cache_do_log_stats(void);
/*
* Fast inline check; the slow path fires roughly every ~5 seconds.
* Call from the miss path only (not every iteration of the inner loop).
*/
static inline void tb_cache_maybe_log_stats(void)
{
if (++tb_cache_stats_call_count < TB_CACHE_LOG_INTERVAL_CALLS) {
return;
}
tb_cache_do_log_stats();
}
/*
* Re-translate the most important recorded hints after a TB flush.
* Called from tb_flush__exclusive_or_serial() to recover quickly
* instead of waiting for on-demand retranslation stutter.
*/
void tb_cache_rewarm_after_flush(CPUState *cpu);
/*
* Free internal state. Called during shutdown.
*/
void tb_cache_cleanup(void);
#else /* !(XBOX && XEMU_OPT_TB_CACHE_HINTS) */
static inline void tb_cache_record_hint(const TranslationBlock *tb) {}
static inline void tb_cache_set_save_target(const char *path, uint32_t game_hash) {}
static inline void tb_cache_save(const char *path, uint32_t game_hash) {}
static inline int tb_cache_load(const char *path, uint32_t game_hash) { return 0; }
static inline void tb_cache_prewarm(CPUState *cpu) {}
static inline uint32_t tb_cache_compute_game_hash(const char *a, const char *b) { return 0; }
static inline void tb_cache_notify_lookup_hit(void) {}
static inline void tb_cache_notify_lookup_miss(void) {}
static inline void tb_cache_maybe_log_stats(void) {}
static inline void tb_cache_rewarm_after_flush(CPUState *cpu) {}
static inline void tb_cache_cleanup(void) {}
#endif /* XBOX && XEMU_OPT_TB_CACHE_HINTS */
#endif /* TB_CACHE_HINTS_H */
+35
View File
@@ -18,6 +18,9 @@
*/
#include "qemu/osdep.h"
#ifdef __ANDROID__
#include <android/log.h>
#endif
#include "qemu/interval-tree.h"
#include "qemu/qtree.h"
#include "exec/cputlb.h"
@@ -34,6 +37,7 @@
#include "tb-context.h"
#include "tb-internal.h"
#include "internal-common.h"
#include "tb-cache-hints.h"
#ifdef CONFIG_USER_ONLY
#include "user/page-protection.h"
#define runstate_is_running() true
@@ -795,6 +799,23 @@ void tb_flush__exclusive_or_serial(void)
tcg_region_reset_all();
/* XXX: flush processor icache at this point if cache flush is expensive */
qatomic_inc(&tb_ctx.tb_flush_count);
#ifdef __ANDROID__
__android_log_print(ANDROID_LOG_WARN, "hakuX-tb",
"TB FLUSH #%u -- all translations destroyed",
qatomic_read(&tb_ctx.tb_flush_count));
#endif
/*
* Re-translate the most important blocks immediately so the
* emulator doesn't stutter while rebuilding on demand.
*/
#ifndef __ANDROID__
if (current_cpu) {
tb_cache_rewarm_after_flush(current_cpu);
}
#endif
qemu_plugin_flush_cb();
}
@@ -968,6 +989,20 @@ static void do_tb_phys_invalidate(TranslationBlock *tb, bool rm_from_page_list)
qatomic_set(&tb_ctx.tb_phys_invalidate_count,
tb_ctx.tb_phys_invalidate_count + 1);
#ifdef XBOX
/* Free superblock metadata if this was a merged superblock. */
if (tb->superblock) {
#ifdef __ANDROID__
__android_log_print(ANDROID_LOG_INFO, "superblock",
"invalidated at 0x%" PRIx64 " (B was 0x%" PRIx64 ")",
(uint64_t)tb->pc,
(uint64_t)tb->superblock->pc_b);
#endif
g_free(tb->superblock);
tb->superblock = NULL;
}
#endif
}
static void tb_phys_invalidate__locked(TranslationBlock *tb)
+13
View File
@@ -24,6 +24,7 @@
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "system/tcg.h"
#include "system/replay.h"
#include "exec/icount.h"
@@ -92,6 +93,18 @@ static void *mttcg_cpu_thread_fn(void *arg)
bql_unlock();
r = tcg_cpu_exec(cpu);
bql_lock();
#ifdef XBOX
{
static int dbg_mttcg = 0;
if (dbg_mttcg < 30) {
error_report("[MTTCG] cpu_exec returned r=%d "
"halted=%d stop=%d exit_req=%d",
r, cpu->halted, cpu->stop,
qatomic_read(&cpu->exit_request));
dbg_mttcg++;
}
}
#endif
switch (r) {
case EXCP_DEBUG:
cpu_handle_guest_debug(cpu);
File diff suppressed because it is too large Load Diff
+14
View File
@@ -148,7 +148,15 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */
/* Start translating. */
#ifdef XBOX
if (tcg_ctx->superblock_append) {
icount_start_insn = NULL;
} else {
icount_start_insn = gen_tb_start(db, cflags);
}
#else
icount_start_insn = gen_tb_start(db, cflags);
#endif
ops->tb_start(db, cpu);
tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */
@@ -204,7 +212,13 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns,
/* Emit code to exit the TB, as indicated by db->is_jmp. */
ops->tb_stop(db, cpu);
#ifdef XBOX
if (!tcg_ctx->superblock_append) {
gen_tb_end(tb, cflags, icount_start_insn, db->num_insns);
}
#else
gen_tb_end(tb, cflags, icount_start_insn, db->num_insns);
#endif
/*
* Manage can_do_io for the translation block: set to false before
+2
View File
@@ -101,6 +101,8 @@ android {
"META-INF/LICENSE*",
"META-INF/NOTICE*"
)
jniLibs.useLegacyPackaging = true
jniLibs.keepDebugSymbols += setOf("**/*.so")
}
compileOptions {
+10
View File
@@ -242,6 +242,15 @@ set(LIBSLIRP_SOURCES
)
if(XEMU_ENABLE_VULKAN)
# --- adrenotools (custom GPU driver loading on Adreno) ---
FetchContent_Declare(
adrenotools
GIT_REPOSITORY "https://github.com/bylaws/libadrenotools"
GIT_TAG "master"
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(adrenotools)
# --- Vulkan deps (volk, glslang, SPIRV-Reflect, VMA) ---
set(VOLK_GIT_REV "0b17a763ba5643e32da1b2152f8140461b3b7345")
FetchContent_Declare(
@@ -1227,6 +1236,7 @@ target_link_libraries(xemu PRIVATE
${glesv3-lib}
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:${vulkan-lib}>
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:volk_static>
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:adrenotools>
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:spirv_reflect_static>
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:glslang>
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:MachineIndependent>
+75
View File
@@ -0,0 +1,75 @@
#include "qemu/osdep.h"
#include "qemu/fast-hash.h"
#ifdef __aarch64__
#include <arm_acle.h>
__attribute__((target("crc")))
uint64_t fast_hash(const uint8_t *data, size_t len)
{
uint32_t c0 = 0x811c9dc5;
uint32_t c1 = 0xc1aee535;
uint32_t c2 = 0x5f356495;
uint32_t c3 = 0x9e3779b9;
const uint64_t *p = (const uint64_t *)data;
size_t n64 = len / 64;
while (n64--) {
c0 = __crc32cd(c0, p[0]);
c1 = __crc32cd(c1, p[1]);
c2 = __crc32cd(c2, p[2]);
c3 = __crc32cd(c3, p[3]);
c0 = __crc32cd(c0, p[4]);
c1 = __crc32cd(c1, p[5]);
c2 = __crc32cd(c2, p[6]);
c3 = __crc32cd(c3, p[7]);
p += 8;
}
const uint8_t *tail = (const uint8_t *)p;
const uint8_t *end = data + len;
while (tail + 8 <= end) {
uint64_t v;
memcpy(&v, tail, 8);
c0 = __crc32cd(c0, v);
tail += 8;
}
uint32_t rem = 0;
while (tail < end) {
rem = (rem << 8) | *tail++;
}
c0 = __crc32cw(c0, rem);
uint64_t hash = ((uint64_t)c0 << 32) | c1;
hash ^= ((uint64_t)c2 << 32) | c3;
return hash;
}
#else
uint64_t fast_hash(const uint8_t *data, size_t len)
{
const uint64_t fnv_offset = 1469598103934665603ULL;
const uint64_t fnv_prime = 1099511628211ULL;
uint64_t hash = fnv_offset;
const uint8_t *end = data + len;
const uint8_t *limit8 = data + (len & ~(size_t)7);
while (data < limit8) {
uint64_t v;
memcpy(&v, data, 8);
hash ^= v;
hash *= fnv_prime;
data += 8;
}
while (data < end) {
hash ^= (uint64_t)*data++;
hash *= fnv_prime;
}
return hash;
}
#endif
+7
View File
@@ -0,0 +1,7 @@
/*
* Stub definitions for hakuX Vulkan backend symbols that are referenced
* by the ported renderer but have no counterpart in the Android build.
*
* Note: tb_cache_stats_lookup_hits/misses are provided by
* accel/tcg/tb-cache-hints.c when XBOX is defined. Do not duplicate them here.
*/
@@ -0,0 +1,48 @@
#ifndef NV2A_VSH_EMULATOR_H
#define NV2A_VSH_EMULATOR_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Nv2aVshProgram {
uint32_t placeholder;
} Nv2aVshProgram;
typedef enum Nv2aVshParseResult {
NV2AVPR_SUCCESS = 0,
NV2AVPR_ERROR = 1,
} Nv2aVshParseResult;
typedef struct Nv2aVshCPUXVSSExecutionState {
float input_regs[4];
} Nv2aVshCPUXVSSExecutionState;
typedef struct Nv2aVshExecutionState {
Nv2aVshCPUXVSSExecutionState *linkage;
float *constants;
} Nv2aVshExecutionState;
Nv2aVshParseResult nv2a_vsh_parse_program(Nv2aVshProgram *program,
const uint32_t *code,
uint32_t code_length);
void nv2a_vsh_program_destroy(Nv2aVshProgram *program);
Nv2aVshExecutionState nv2a_vsh_emu_initialize_xss_execution_state(
Nv2aVshCPUXVSSExecutionState *linkage,
float *constants);
void nv2a_vsh_emu_execute_track_context_writes(
Nv2aVshExecutionState *state,
const Nv2aVshProgram *program,
bool *constants_dirty);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,36 @@
#include "nv2a_vsh_emulator.h"
Nv2aVshParseResult nv2a_vsh_parse_program(Nv2aVshProgram *program,
const uint32_t *code,
uint32_t code_length)
{
(void)program;
(void)code;
(void)code_length;
return NV2AVPR_SUCCESS;
}
void nv2a_vsh_program_destroy(Nv2aVshProgram *program)
{
(void)program;
}
Nv2aVshExecutionState nv2a_vsh_emu_initialize_xss_execution_state(
Nv2aVshCPUXVSSExecutionState *linkage,
float *constants)
{
Nv2aVshExecutionState state;
state.linkage = linkage;
state.constants = constants;
return state;
}
void nv2a_vsh_emu_execute_track_context_writes(
Nv2aVshExecutionState *state,
const Nv2aVshProgram *program,
bool *constants_dirty)
{
(void)state;
(void)program;
(void)constants_dirty;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef SAMPLERATE_H
#define SAMPLERATE_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SRC_STATE SRC_STATE;
typedef long (*src_callback_t)(void *cb_data, float **data);
enum {
SRC_SINC_FASTEST = 2,
SRC_LINEAR = 4,
};
SRC_STATE *src_callback_new(src_callback_t cb, int converter_type, int channels,
int *error, void *cb_data);
long src_callback_read(SRC_STATE *state, double ratio, long frames, float *data);
SRC_STATE *src_delete(SRC_STATE *state);
int src_reset(SRC_STATE *state);
const char *src_strerror(int error);
void src_float_to_short_array(const float *in, short *out, int len);
#ifdef __cplusplus
}
#endif
#endif /* SAMPLERATE_H */
+209
View File
@@ -0,0 +1,209 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#if defined(__aarch64__)
#include <arm_neon.h>
#endif
#include "samplerate.h"
struct SRC_STATE {
src_callback_t cb;
void *cb_data;
int channels;
float *input_buf;
int input_buf_len;
int input_buf_used;
double input_pos;
};
SRC_STATE *src_callback_new(src_callback_t cb, int converter_type, int channels,
int *error, void *cb_data)
{
(void)converter_type;
if (error) {
*error = 0;
}
SRC_STATE *state = (SRC_STATE *)calloc(1, sizeof(*state));
if (!state) {
if (error) {
*error = -1;
}
return NULL;
}
state->cb = cb;
state->cb_data = cb_data;
state->channels = channels;
state->input_buf = NULL;
state->input_buf_len = 0;
state->input_buf_used = 0;
state->input_pos = 0.0;
return state;
}
static int src_refill_input(SRC_STATE *state)
{
int ch = state->channels;
int consumed = (int)state->input_pos;
state->input_pos -= consumed;
if (state->input_pos < 0.0) {
state->input_pos = 0.0;
}
int remaining = state->input_buf_len - state->input_buf_used - consumed;
if (remaining < 0) {
remaining = 0;
}
float *new_data = NULL;
long got = state->cb(state->cb_data, &new_data);
if (got <= 0 || new_data == NULL) {
if (remaining > 0 && state->input_buf) {
memmove(state->input_buf,
state->input_buf + (state->input_buf_used + consumed) * ch,
sizeof(float) * remaining * ch);
state->input_buf_len = remaining;
state->input_buf_used = 0;
}
return remaining >= 2;
}
int new_len = remaining + (int)got;
float *buf = (float *)malloc(sizeof(float) * new_len * ch);
if (!buf) {
return 0;
}
if (remaining > 0 && state->input_buf) {
memcpy(buf,
state->input_buf + (state->input_buf_used + consumed) * ch,
sizeof(float) * remaining * ch);
}
memcpy(buf + remaining * ch, new_data, sizeof(float) * got * ch);
free(state->input_buf);
state->input_buf = buf;
state->input_buf_len = new_len;
state->input_buf_used = 0;
return 1;
}
long src_callback_read(SRC_STATE *state, double ratio, long frames, float *data)
{
if (!state || !state->cb || !data || frames <= 0) {
return 0;
}
if (ratio <= 0.0) {
ratio = 1.0;
}
int ch = state->channels;
double step = 1.0 / ratio;
long out_frames = 0;
while (out_frames < frames) {
int avail = state->input_buf_len - state->input_buf_used;
int needed_idx = (int)state->input_pos + 1;
if (needed_idx >= avail) {
if (!src_refill_input(state)) {
break;
}
avail = state->input_buf_len - state->input_buf_used;
if (avail < 2) {
break;
}
}
int idx0 = (int)state->input_pos;
int idx1 = idx0 + 1;
if (idx1 >= avail) {
break;
}
float frac = (float)(state->input_pos - idx0);
float *s0 = state->input_buf + (state->input_buf_used + idx0) * ch;
float *s1 = state->input_buf + (state->input_buf_used + idx1) * ch;
for (int c = 0; c < ch; c++) {
data[out_frames * ch + c] = s0[c] + frac * (s1[c] - s0[c]);
}
out_frames++;
state->input_pos += step;
}
return out_frames;
}
SRC_STATE *src_delete(SRC_STATE *state)
{
if (state) {
free(state->input_buf);
free(state);
}
return NULL;
}
int src_reset(SRC_STATE *state)
{
if (state) {
free(state->input_buf);
state->input_buf = NULL;
state->input_buf_len = 0;
state->input_buf_used = 0;
state->input_pos = 0.0;
}
return 0;
}
const char *src_strerror(int error)
{
(void)error;
return "libsamplerate stub";
}
void src_float_to_short_array(const float *in, short *out, int len)
{
if (!in || !out || len <= 0) {
return;
}
#if defined(__aarch64__)
float32x4_t scale = vdupq_n_f32(32767.0f);
float32x4_t hi = vdupq_n_f32(1.0f);
float32x4_t lo = vdupq_n_f32(-1.0f);
int i = 0;
for (; i + 4 <= len; i += 4) {
float32x4_t v = vld1q_f32(&in[i]);
v = vminq_f32(vmaxq_f32(v, lo), hi);
v = vmulq_f32(v, scale);
int32x4_t iv = vcvtq_s32_f32(v);
int16x4_t sv = vqmovn_s32(iv);
vst1_s16(&out[i], sv);
}
for (; i < len; i++) {
float v = in[i];
if (v > 1.0f) {
v = 1.0f;
} else if (v < -1.0f) {
v = -1.0f;
}
out[i] = (short)(v * 32767.0f);
}
#else
for (int i = 0; i < len; ++i) {
float v = in[i];
if (v > 1.0f) {
v = 1.0f;
} else if (v < -1.0f) {
v = -1.0f;
}
out[i] = (short)(v * 32767.0f);
}
#endif
}
+330 -36
View File
@@ -27,8 +27,40 @@
#include <errno.h>
#include <unistd.h>
#ifdef CONFIG_VULKAN
#include <adrenotools/driver.h>
#include <dlfcn.h>
#include <volk.h>
static void *g_custom_vulkan_library = nullptr;
static void *g_system_vulkan_library = nullptr;
extern "C" PFN_vkGetInstanceProcAddr xemu_android_get_vk_proc_addr(void)
{
void *handle = g_custom_vulkan_library ? g_custom_vulkan_library
: g_system_vulkan_library;
if (!handle) {
return nullptr;
}
return reinterpret_cast<PFN_vkGetInstanceProcAddr>(
dlsym(handle, "vkGetInstanceProcAddr"));
}
#endif
#include "xemu-settings.h"
extern "C" void xemu_set_fp_safe(bool enable);
extern "C" void xemu_set_fp_jit(bool enable);
extern "C" bool xemu_get_fp_safe(void);
extern "C" bool xemu_get_fp_jit(void);
extern "C" void xemu_set_fast_fences(bool enable);
extern "C" void xemu_set_draw_reorder(bool enable);
extern "C" void xemu_set_draw_merge(bool enable);
extern "C" void xemu_set_bindless_textures(bool enable);
extern "C" void xemu_set_async_compile(bool enable);
extern "C" void xemu_set_frame_skip(bool enable);
extern "C" void xemu_set_submit_frames(int count);
struct Error;
struct AddfdInfo;
extern "C" AddfdInfo* monitor_fdset_add_fd(int fd, bool has_fdset_id,
@@ -67,6 +99,8 @@ static void ConfigureNativeDebugLogging(JNIEnv* env, jobject activity);
static void ApplyHrtfDefaultOffMigration(JNIEnv* env, jobject activity);
static bool NativeDebugLoggingEnabled();
static void AppendNativeDebugLog(const char* level, const char* message);
static std::string GetPreferredPersistentStoragePath();
static std::string GetInternalPersistentStoragePath();
static void LogInfo(const char* msg) {
if (!NativeDebugLoggingEnabled()) {
@@ -136,6 +170,25 @@ static bool EnsureDirExists(const std::string& path) {
return errno == EEXIST;
}
static std::string GetInternalPersistentStoragePath() {
const char* internal = SDL_AndroidGetInternalStoragePath();
if (!internal || internal[0] == '\0') {
return {};
}
return std::string(internal);
}
static std::string GetPreferredPersistentStoragePath() {
int extState = SDL_AndroidGetExternalStorageState();
if (extState & SDL_ANDROID_EXTERNAL_STORAGE_WRITE) {
const char* external = SDL_AndroidGetExternalStoragePath();
if (external && external[0] != '\0') {
return std::string(external);
}
}
return GetInternalPersistentStoragePath();
}
static bool FileExists(const std::string& path) {
if (path.empty()) return false;
struct stat st {};
@@ -737,6 +790,40 @@ static int GetPrefInt(JNIEnv* env, jobject activity, const char* key, int defVal
return out;
}
static bool PrefContainsKey(JNIEnv* env, jobject activity, const char* key) {
if (!env || !activity || !key || key[0] == '\0') {
return false;
}
bool containsKey = false;
jclass activityClass = env->GetObjectClass(activity);
if (!activityClass) return false;
jmethodID getPrefs = env->GetMethodID(activityClass, "getSharedPreferences",
"(Ljava/lang/String;I)Landroid/content/SharedPreferences;");
env->DeleteLocalRef(activityClass);
if (!getPrefs) return false;
jstring prefsName = env->NewStringUTF(kPrefsName);
if (!prefsName) return false;
jobject prefs = env->CallObjectMethod(activity, getPrefs, prefsName, 0);
env->DeleteLocalRef(prefsName);
if (HasException(env, "getSharedPreferences") || !prefs) return false;
jclass prefsClass = env->GetObjectClass(prefs);
if (prefsClass) {
jmethodID contains = env->GetMethodID(prefsClass, "contains",
"(Ljava/lang/String;)Z");
if (contains) {
jstring jkey = env->NewStringUTF(key);
if (jkey) {
containsKey = env->CallBooleanMethod(prefs, contains, jkey) == JNI_TRUE;
HasException(env, "contains");
env->DeleteLocalRef(jkey);
}
}
env->DeleteLocalRef(prefsClass);
}
env->DeleteLocalRef(prefs);
return containsKey;
}
static std::string BuildRuntimeOverrideKey(const char* key) {
if (!key || key[0] == '\0') {
return {};
@@ -805,6 +892,25 @@ static int GetEffectivePrefInt(JNIEnv* env, jobject activity, const char* key,
return GetPrefInt(env, activity, key, defValue);
}
static bool GetEffectiveFpJitPref(JNIEnv* env, jobject activity,
bool defValue) {
const std::string runtimeKey = BuildRuntimeOverrideKey("setting_fp_jit");
if (!runtimeKey.empty()) {
const std::string overrideValue =
GetPrefString(env, activity, runtimeKey.c_str());
bool parsed = false;
if (ParseOverrideBool(overrideValue, &parsed)) {
return parsed;
}
}
if (PrefContainsKey(env, activity, "setting_fp_jit")) {
return GetPrefBool(env, activity, "setting_fp_jit", defValue);
}
return GetEffectivePrefBool(env, activity, "setting_hard_fpu", defValue);
}
static void ConfigureNativeDebugLogging(JNIEnv* env, jobject activity) {
g_native_debug_logging_enabled.store(
GetPrefBool(env, activity, kDebugLogPrefKey, false));
@@ -1119,8 +1225,9 @@ struct EmulatorSettings {
int surface_scale = 1; // 1, 2, or 3
int system_memory_mib = 64; // 64 or 128
std::string tcg_thread = "multi"; // "single" or "multi"
std::string renderer = "opengl"; // "vulkan" or "opengl"
std::string renderer = "vulkan"; // "vulkan" or "opengl"
std::string filtering = "linear"; // "linear" or "nearest"
int display_mode = 0; // 0=stretch, 1=4:3, 2=16:9
bool use_dsp = false;
bool hrtf = false;
bool cache_shaders = true;
@@ -1172,6 +1279,7 @@ static bool WriteConfigToml(const std::string& config_path,
toml::table* general = EnsureTable(tbl, "general");
toml::table* display = EnsureTable(tbl, "display");
toml::table* display_quality = EnsureTable(*display, "quality");
toml::table* display_ui = EnsureTable(*display, "ui");
toml::table* display_window = EnsureTable(*display, "window");
toml::table* audio = EnsureTable(tbl, "audio");
toml::table* audio_vp = EnsureTable(*audio, "vp");
@@ -1181,8 +1289,9 @@ static bool WriteConfigToml(const std::string& config_path,
toml::table* sys = EnsureTable(tbl, "sys");
toml::table* perf = EnsureTable(tbl, "perf");
toml::table* files = EnsureTable(*sys, "files");
if (!general || !display || !display_quality || !display_window || !audio ||
!audio_vp || !android || !net || !net_nat || !sys || !perf || !files) {
if (!general || !display || !display_quality || !display_ui ||
!display_window || !audio || !audio_vp || !android || !net ||
!net_nat || !sys || !perf || !files) {
LogErrorFmt("Failed to build config tables at %s", config_path.c_str());
return false;
}
@@ -1203,10 +1312,19 @@ static bool WriteConfigToml(const std::string& config_path,
if (scale > 3) scale = 3;
display_quality->insert_or_assign("surface_scale", scale);
}
{
const char *aspect_ratio = "fit";
if (settings.display_mode == 1) {
aspect_ratio = "4:3";
} else if (settings.display_mode == 2) {
aspect_ratio = "16:9";
}
display_ui->insert_or_assign("aspect_ratio", aspect_ratio);
}
audio->insert_or_assign("use_dsp", settings.use_dsp);
audio->insert_or_assign("hrtf", settings.hrtf);
perf->insert_or_assign("cache_shaders", settings.cache_shaders);
perf->insert_or_assign("hard_fpu", settings.hard_fpu);
perf->insert_or_assign("fp_jit", settings.hard_fpu);
android->insert_or_assign("tcg_thread",
(settings.tcg_thread == "single") ? "single" : "multi");
android->insert_or_assign("frame_rate_limit", 60);
@@ -1246,8 +1364,6 @@ static bool WriteConfigToml(const std::string& config_path,
return true;
}
extern "C" void xemu_android_set_display_mode_setting(int mode);
static SetupFiles SyncSetupFiles() {
SetupFiles out{};
JNIEnv* env = GetEnv();
@@ -1363,8 +1479,7 @@ static SetupFiles SyncSetupFiles() {
GetEffectivePrefBool(env, activity, kHrtfPrefKey, false);
emuSettings.cache_shaders =
GetEffectivePrefBool(env, activity, "setting_cache_shaders", true);
emuSettings.hard_fpu =
GetEffectivePrefBool(env, activity, "setting_hard_fpu", true);
emuSettings.hard_fpu = GetEffectiveFpJitPref(env, activity, true);
emuSettings.skip_boot_anim =
GetEffectivePrefBool(env, activity, "setting_skip_boot_anim", false);
emuSettings.network_enabled =
@@ -1403,36 +1518,54 @@ static SetupFiles SyncSetupFiles() {
}
}
int displayMode = GetEffectivePrefInt(env, activity, "setting_display_mode", 0);
xemu_android_set_display_mode_setting(displayMode);
unsetenv("XEMU_VULKAN_DRIVER");
const std::string vulkanDriverPath =
GetPrefString(env, activity, "setting_vulkan_driver_path");
if (!vulkanDriverPath.empty() && FileExists(vulkanDriverPath)) {
chmod(vulkanDriverPath.c_str(), 0755);
setenv("XEMU_VULKAN_DRIVER", vulkanDriverPath.c_str(), 1);
LogInfoFmt("Custom Vulkan driver staged: %s", vulkanDriverPath.c_str());
} else {
if (!vulkanDriverPath.empty()) {
LogErrorFmt("Configured Vulkan driver not found: %s",
vulkanDriverPath.c_str());
}
const std::string vulkanDriverUri =
GetPrefString(env, activity, "setting_vulkan_driver_uri");
if (!vulkanDriverUri.empty()) {
std::string driverPath = base + "/vulkan_driver.so";
if (CopyUriToPath(env, activity, vulkanDriverUri, driverPath)) {
chmod(driverPath.c_str(), 0755);
setenv("XEMU_VULKAN_DRIVER", driverPath.c_str(), 1);
LogInfoFmt("Custom Vulkan driver staged: %s", driverPath.c_str());
} else {
LogError("Failed to copy custom Vulkan driver; using system default");
}
}
emuSettings.display_mode =
GetEffectivePrefInt(env, activity, "setting_display_mode", 0);
if (emuSettings.display_mode < 0 || emuSettings.display_mode > 2) {
emuSettings.display_mode = 0;
}
const bool fpSafe = GetEffectivePrefBool(env, activity, "fp_safe", true);
const bool fpJit =
GetEffectivePrefBool(env, activity, "fp_jit", emuSettings.hard_fpu);
const bool fastFences =
GetEffectivePrefBool(env, activity, "fast_fences", false);
const bool drawReorder =
GetEffectivePrefBool(env, activity, "draw_reorder", false);
const bool drawMerge =
GetEffectivePrefBool(env, activity, "draw_merge", false);
const bool bindlessTextures =
GetEffectivePrefBool(env, activity, "bindless_textures", false);
const bool asyncCompile =
GetEffectivePrefBool(env, activity, "async_compile", false);
const bool frameSkip =
GetEffectivePrefBool(env, activity, "frame_skip", false);
const int submitFrames =
GetEffectivePrefInt(env, activity, "submit_frames", 2);
xemu_set_fp_safe(fpSafe);
xemu_set_fp_jit(fpJit);
xemu_set_fast_fences(fastFences);
xemu_set_draw_reorder(drawReorder);
xemu_set_draw_merge(drawMerge);
xemu_set_bindless_textures(bindlessTextures);
xemu_set_async_compile(asyncCompile);
xemu_set_frame_skip(frameSkip);
xemu_set_submit_frames(submitFrames);
LogInfoInt("Config runtime fp_safe=%d", fpSafe ? 1 : 0);
LogInfoInt("Config runtime fp_jit_pref=%d", fpJit ? 1 : 0);
LogInfoInt("Config runtime fast_fences=%d", fastFences ? 1 : 0);
LogInfoInt("Config runtime draw_reorder=%d", drawReorder ? 1 : 0);
LogInfoInt("Config runtime draw_merge=%d", drawMerge ? 1 : 0);
LogInfoInt("Config runtime bindless_textures=%d",
bindlessTextures ? 1 : 0);
LogInfoInt("Config runtime async_compile=%d", asyncCompile ? 1 : 0);
LogInfoInt("Config runtime frame_skip=%d", frameSkip ? 1 : 0);
LogInfoInt("Config runtime submit_frames=%d", submitFrames);
// Custom Vulkan driver loading is handled by GpuDriverHelper via adrenotools
// in MainActivity.loadLibraries(), before the native library initializes.
out.config_path = base + "/xemu.toml";
WriteConfigToml(out.config_path, out.mcpx, out.flash, out.hdd, out.dvd, out.eeprom, emuSettings);
LogInfoFmt("SyncSetupFiles: config %s", out.config_path.c_str());
@@ -1453,6 +1586,19 @@ extern "C" void xemu_android_display_wait_ready(void);
extern "C" void xemu_android_display_loop(void);
extern "C" void xemu_android_set_inline_aio_crash_flag_path(const char* path);
#ifndef XEMU_OPT_TB_CACHE_HINTS
#define XEMU_OPT_TB_CACHE_HINTS 1
#endif
#if XEMU_OPT_TB_CACHE_HINTS
extern "C" void tb_cache_set_save_target(const char* path, uint32_t game_hash);
extern "C" void tb_cache_save(const char* path, uint32_t game_hash);
extern "C" int tb_cache_load(const char* path, uint32_t game_hash);
extern "C" uint32_t tb_cache_compute_game_hash(const char* bootrom_path,
const char* flashrom_path);
extern "C" void tb_cache_cleanup(void);
#endif
struct QemuLaunchContext {
int argc;
char** argv;
@@ -1481,9 +1627,62 @@ extern "C" int xemu_android_main(int argc, char** argv) {
}
LogInfo("xemu_android_main: qemu_init");
qemu_init(argc, argv);
#if XEMU_OPT_TB_CACHE_HINTS
std::string cache_storage = GetPreferredPersistentStoragePath();
std::string internal_storage = GetInternalPersistentStoragePath();
if (!cache_storage.empty()) {
std::string cache_dir = cache_storage + "/x1box";
EnsureDirExists(cache_dir);
char cache_path[PATH_MAX];
snprintf(cache_path, sizeof(cache_path), "%s/tb_cache.bin",
cache_dir.c_str());
std::string load_path = cache_path;
if (load_path != internal_storage + "/x1box/tb_cache.bin" &&
!FileExists(load_path) && !internal_storage.empty()) {
std::string internal_cache_dir = internal_storage + "/x1box";
std::string internal_cache_path = internal_cache_dir + "/tb_cache.bin";
if (FileExists(internal_cache_path)) {
load_path = internal_cache_path;
}
}
uint32_t game_hash = tb_cache_compute_game_hash(
g_config.sys.files.bootrom_path, g_config.sys.files.flashrom_path);
game_hash ^= (xemu_get_fp_safe() ? 0x1u : 0u)
| (xemu_get_fp_jit() ? 0x2u : 0u);
tb_cache_set_save_target(cache_path, game_hash);
int nhints = tb_cache_load(load_path.c_str(), game_hash);
if (NativeDebugLoggingEnabled()) {
char tb_cache_msg[PATH_MAX + 64] = {};
std::snprintf(tb_cache_msg, sizeof(tb_cache_msg),
"TB cache loaded %d hints from %s", nhints,
load_path.c_str());
LogInfo(tb_cache_msg);
}
}
#endif
LogInfo("xemu_android_main: qemu_main");
int rc = qemu_main();
LogErrorInt("xemu_android_main: qemu_main returned %d", rc);
#if XEMU_OPT_TB_CACHE_HINTS
std::string save_storage = GetPreferredPersistentStoragePath();
if (!save_storage.empty()) {
std::string cache_dir = save_storage + "/x1box";
EnsureDirExists(cache_dir);
char cache_path[PATH_MAX];
snprintf(cache_path, sizeof(cache_path), "%s/tb_cache.bin",
cache_dir.c_str());
uint32_t game_hash = tb_cache_compute_game_hash(
g_config.sys.files.bootrom_path, g_config.sys.files.flashrom_path);
game_hash ^= (xemu_get_fp_safe() ? 0x1u : 0u)
| (xemu_get_fp_jit() ? 0x2u : 0u);
tb_cache_save(cache_path, game_hash);
}
tb_cache_cleanup();
#endif
return rc;
}
@@ -1591,6 +1790,7 @@ extern "C" int SDL_main(int argc, char* argv[]) {
g_config.perf.cache_shaders = true;
LogInfoInt("Config final show_welcome=%d", g_config.general.show_welcome ? 1 : 0);
LogInfoInt("Config final cache_shaders=%d", g_config.perf.cache_shaders ? 1 : 0);
LogInfoInt("Config final fp_jit=%d", g_config.perf.fp_jit ? 1 : 0);
LogInfoFmt("Config final renderer=%s",
RendererName(g_config.display.renderer));
LogInfoFmt("Config final bootrom=%s", g_config.sys.files.bootrom_path ? g_config.sys.files.bootrom_path : "(null)");
@@ -1708,3 +1908,97 @@ extern "C" int SDL_main(int argc, char* argv[]) {
SDL_Quit();
return 0;
}
#ifdef CONFIG_VULKAN
extern "C" JNIEXPORT jboolean JNICALL
Java_com_izzy2lost_x1box_GpuDriverHelper_nativeSupportsCustomDriverLoading(JNIEnv *, jclass)
{
return access("/dev/kgsl-3d0", F_OK) == 0 ? JNI_TRUE : JNI_FALSE;
}
extern "C" JNIEXPORT void JNICALL
Java_com_izzy2lost_x1box_GpuDriverHelper_nativeInitializeDriver(
JNIEnv *env, jclass,
jstring hookLibDir, jstring customDriverDir,
jstring customDriverName)
{
const char *hook_dir = hookLibDir ? env->GetStringUTFChars(hookLibDir, nullptr) : nullptr;
const char *driver_dir = customDriverDir ? env->GetStringUTFChars(customDriverDir, nullptr) : nullptr;
const char *driver_name = customDriverName ? env->GetStringUTFChars(customDriverName, nullptr) : nullptr;
void *handle = nullptr;
g_custom_vulkan_library = nullptr;
g_system_vulkan_library = nullptr;
if (driver_name && driver_name[0] != '\0') {
__android_log_print(ANDROID_LOG_INFO, kLogTag,
"Loading custom Vulkan driver: %s from %s",
driver_name, driver_dir ? driver_dir : "(null)");
handle = adrenotools_open_libvulkan(
RTLD_NOW,
ADRENOTOOLS_DRIVER_CUSTOM,
nullptr,
hook_dir,
driver_dir,
driver_name,
nullptr,
nullptr);
if (handle) {
g_custom_vulkan_library = handle;
__android_log_print(ANDROID_LOG_INFO, kLogTag,
"Custom Vulkan driver loaded successfully via adrenotools");
} else {
__android_log_print(ANDROID_LOG_WARN, kLogTag,
"adrenotools failed to load custom driver, will fall back to system default");
}
} else {
__android_log_print(ANDROID_LOG_INFO, kLogTag,
"No custom driver specified, initializing system Vulkan via adrenotools");
handle = adrenotools_open_libvulkan(
RTLD_NOW,
0,
nullptr,
hook_dir,
nullptr,
nullptr,
nullptr,
nullptr);
if (handle) {
g_system_vulkan_library = handle;
__android_log_print(ANDROID_LOG_INFO, kLogTag,
"System Vulkan initialized via adrenotools; exposing hooked vkGetInstanceProcAddr");
} else {
__android_log_print(ANDROID_LOG_WARN, kLogTag,
"adrenotools failed to initialize system Vulkan driver, using plain system loader");
}
}
if (driver_name) env->ReleaseStringUTFChars(customDriverName, driver_name);
if (driver_dir) env->ReleaseStringUTFChars(customDriverDir, driver_dir);
if (hook_dir) env->ReleaseStringUTFChars(hookLibDir, hook_dir);
}
#endif
extern "C" void xemu_android_pause_emulation(void);
extern "C" void xemu_android_resume_emulation(void);
extern "C" void xemu_android_request_exit(void);
extern "C" JNIEXPORT void JNICALL
Java_com_izzy2lost_x1box_MainActivity_nativePauseEmulation(JNIEnv *, jobject)
{
xemu_android_pause_emulation();
}
extern "C" JNIEXPORT void JNICALL
Java_com_izzy2lost_x1box_MainActivity_nativeResumeEmulation(JNIEnv *, jobject)
{
xemu_android_resume_emulation();
}
extern "C" JNIEXPORT void JNICALL
Java_com_izzy2lost_x1box_MainActivity_nativeExitEmulation(JNIEnv *, jobject)
{
xemu_android_request_exit();
}
@@ -40,7 +40,7 @@ static void xemu_settings_apply_defaults(void)
g_config.general.show_welcome = true;
g_config.general.updates.check = true;
g_config.general.skip_boot_anim = false;
g_config.general.skip_boot_anim = true;
g_config.general.last_viewed_menu_index = 0;
g_config.input.auto_bind = true;
@@ -72,8 +72,8 @@ static void xemu_settings_apply_defaults(void)
g_config.input.keyboard_controller_scancode_map.rstick_down = 14;
g_config.input.keyboard_controller_scancode_map.rtrigger = 18;
g_config.display.renderer = CONFIG_DISPLAY_RENDERER_OPENGL;
g_config.display.filtering = CONFIG_DISPLAY_FILTERING_LINEAR;
g_config.display.renderer = CONFIG_DISPLAY_RENDERER_VULKAN;
g_config.display.filtering = CONFIG_DISPLAY_FILTERING_NEAREST;
g_config.display.quality.surface_scale = 1;
g_config.display.window.fullscreen_on_startup = false;
g_config.display.window.fullscreen_exclusive = false;
@@ -81,7 +81,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;
@@ -105,7 +105,7 @@ static void xemu_settings_apply_defaults(void)
g_config.sys.mem_limit = CONFIG_SYS_MEM_LIMIT_64;
g_config.sys.avpack = CONFIG_SYS_AVPACK_HDTV;
g_config.perf.hard_fpu = true;
g_config.perf.fp_jit = true;
g_config.perf.cache_shaders = true;
}
@@ -287,10 +287,10 @@ bool xemu_settings_load(void)
xemu_settings_apply_defaults();
error_msg.clear();
setenv("XEMU_ANDROID_FORCE_CPU_BLIT", "0", 1);
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') {
@@ -361,9 +361,35 @@ bool xemu_settings_load(void)
g_config.display.window.vsync = *vsync;
}
auto display_ui = display["ui"];
if (auto aspect_ratio =
display_ui["aspect_ratio"].value<std::string>()) {
if (*aspect_ratio == "fit") {
g_config.display.ui.fit = CONFIG_DISPLAY_UI_FIT_STRETCH;
} else if (*aspect_ratio == "auto") {
g_config.display.ui.fit = CONFIG_DISPLAY_UI_FIT_SCALE;
g_config.display.ui.aspect_ratio =
CONFIG_DISPLAY_UI_ASPECT_RATIO_AUTO;
} else if (*aspect_ratio == "native") {
g_config.display.ui.fit = CONFIG_DISPLAY_UI_FIT_SCALE;
g_config.display.ui.aspect_ratio =
CONFIG_DISPLAY_UI_ASPECT_RATIO_NATIVE;
} else if (*aspect_ratio == "4:3") {
g_config.display.ui.fit = CONFIG_DISPLAY_UI_FIT_SCALE;
g_config.display.ui.aspect_ratio =
CONFIG_DISPLAY_UI_ASPECT_RATIO_4X3;
} else if (*aspect_ratio == "16:9") {
g_config.display.ui.fit = CONFIG_DISPLAY_UI_FIT_SCALE;
g_config.display.ui.aspect_ratio =
CONFIG_DISPLAY_UI_ASPECT_RATIO_16X9;
}
}
// Performance settings
if (auto hard_fpu = perf["hard_fpu"].value<bool>()) {
g_config.perf.hard_fpu = *hard_fpu;
if (auto fp_jit = perf["fp_jit"].value<bool>()) {
g_config.perf.fp_jit = *fp_jit;
} else if (auto hard_fpu = perf["hard_fpu"].value<bool>()) {
g_config.perf.fp_jit = *hard_fpu;
}
if (auto cache_shaders = perf["cache_shaders"].value<bool>()) {
g_config.perf.cache_shaders = *cache_shaders;
@@ -769,3 +795,53 @@ void xemu_settings_reset_controller_mapping(const char *guid)
void xemu_settings_reset_keyboard_mapping(void)
{
}
extern "C" void xemu_set_fp_jit(bool enable)
{
g_config.perf.fp_jit = enable;
}
extern "C" bool xemu_get_fp_jit(void)
{
return g_config.perf.fp_jit;
}
/*
* Android still exposes a few runtime Vulkan toggles from an older fork, but
* the synced hakuX renderer does not consume them anymore. Keep the exports so
* JNI/front-end code links cleanly without reintroducing fork-only behavior.
*/
extern "C" void xemu_set_fast_fences(bool enable)
{
(void)enable;
}
extern "C" void xemu_set_draw_reorder(bool enable)
{
(void)enable;
}
extern "C" void xemu_set_draw_merge(bool enable)
{
(void)enable;
}
extern "C" void xemu_set_bindless_textures(bool enable)
{
(void)enable;
}
extern "C" void xemu_set_async_compile(bool enable)
{
(void)enable;
}
extern "C" void xemu_set_frame_skip(bool enable)
{
(void)enable;
}
extern "C" void xemu_set_submit_frames(int count)
{
(void)count;
}
@@ -83,7 +83,7 @@ object FrontendLaunchHelper {
val extras = intent.extras
if (extras != null) {
for (key in stringExtraKeys) {
when (val value = extras.get(key)) {
when (val value = @Suppress("DEPRECATION") extras.get(key)) {
is Uri -> candidates += "extra:$key" to value
is String -> candidates += "extra:$key" to value
is CharSequence -> candidates += "extra:$key" to value.toString()
@@ -0,0 +1,181 @@
package com.izzy2lost.x1box
import android.content.Context
import android.net.Uri
import android.os.Build
import android.util.Log
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.ZipFile
object GpuDriverHelper {
private const val TAG = "GpuDriverHelper"
private const val META_JSON = "meta.json"
private lateinit var appContext: Context
val driverInstallDir: String get() = appContext.filesDir.absolutePath + "/gpu_driver/"
val driverStorageDir: String get() = appContext.getExternalFilesDir(null)!!.absolutePath + "/gpu_drivers/"
val hookLibDir: String get() = appContext.applicationInfo.nativeLibraryDir + "/"
fun init(context: Context) {
appContext = context.applicationContext
File(driverInstallDir).mkdirs()
File(driverStorageDir).mkdirs()
}
fun supportsCustomDriverLoading(): Boolean {
return File("/dev/kgsl-3d0").exists()
}
fun initializeDriver(customDriverName: String? = null) {
nativeInitializeDriver(hookLibDir, driverInstallDir, customDriverName)
}
fun installDriverFromUri(context: Context, uri: Uri): Boolean {
init(context)
val tmpFile = File(driverStorageDir, "driver_tmp.zip")
try {
context.contentResolver.openInputStream(uri)?.use { input ->
FileOutputStream(tmpFile).use { output ->
input.copyTo(output)
}
} ?: return false
} catch (e: IOException) {
Log.e(TAG, "Failed to copy driver URI", e)
tmpFile.delete()
return false
}
val metadata = readMetadata(tmpFile)
if (metadata == null) {
Log.e(TAG, "Invalid driver ZIP: no meta.json found")
tmpFile.delete()
return false
}
if (metadata.minApi > Build.VERSION.SDK_INT) {
Log.e(TAG, "Driver requires API ${metadata.minApi}, device is ${Build.VERSION.SDK_INT}")
tmpFile.delete()
return false
}
val namedFile = File(driverStorageDir, metadata.name?.replace(" ", "_") + ".zip")
tmpFile.renameTo(namedFile)
return installDriver(namedFile)
}
fun installDriver(driverZip: File): Boolean {
val installDir = File(driverInstallDir)
installDir.deleteRecursively()
installDir.mkdirs()
try {
ZipFile(driverZip).use { zip ->
zip.entries().asSequence().forEach { entry ->
if (entry.isDirectory) {
File(installDir, entry.name).mkdirs()
} else {
val outFile = File(installDir, entry.name)
outFile.parentFile?.mkdirs()
zip.getInputStream(entry).use { input ->
FileOutputStream(outFile).use { output ->
input.copyTo(output)
}
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to extract driver", e)
return false
}
return true
}
fun installDefaultDriver() {
File(driverInstallDir).deleteRecursively()
File(driverInstallDir).mkdirs()
}
fun getInstalledDriverName(): String? {
val metaFile = File(driverInstallDir, META_JSON)
if (!metaFile.exists()) return null
return try {
val json = JSONObject(metaFile.readText())
json.optString("name", null)
} catch (e: Exception) {
null
}
}
fun getInstalledDriverLibrary(): String? {
val metaFile = File(driverInstallDir, META_JSON)
if (!metaFile.exists()) return null
return try {
val json = JSONObject(metaFile.readText())
json.optString("libraryName", null)
} catch (e: Exception) {
null
}
}
fun getAvailableDrivers(): List<DriverMetadata> {
val dir = File(driverStorageDir)
if (!dir.exists()) return emptyList()
return dir.listFiles()
?.filter { it.extension == "zip" }
?.mapNotNull { readMetadata(it)?.copy(path = it.absolutePath) }
?.sortedBy { it.name }
?: emptyList()
}
fun readMetadata(zipFile: File): DriverMetadata? {
if (!zipFile.exists()) return null
try {
ZipFile(zipFile).use { zip ->
val entries = zip.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
if (!entry.isDirectory && entry.name.lowercase().endsWith(".json")) {
zip.getInputStream(entry).use { input ->
val text = input.bufferedReader().readText()
val json = JSONObject(text)
return DriverMetadata(
name = json.optString("name", null),
description = json.optString("description", null),
author = json.optString("author", null),
libraryName = json.optString("libraryName", null),
minApi = json.optInt("minApi", 0),
path = zipFile.absolutePath
)
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to read driver metadata from ${zipFile.name}", e)
}
return null
}
private external fun nativeInitializeDriver(
hookLibDir: String?,
customDriverDir: String?,
customDriverName: String?
)
data class DriverMetadata(
val name: String? = null,
val description: String? = null,
val author: String? = null,
val libraryName: String? = null,
val minApi: Int = 0,
val path: String? = null
)
}

Some files were not shown because too many files have changed in this diff Show More