mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
sloppy stuff
This commit is contained in:
@@ -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(); \
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
+555
-4
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -101,6 +101,8 @@ android {
|
||||
"META-INF/LICENSE*",
|
||||
"META-INF/NOTICE*"
|
||||
)
|
||||
jniLibs.useLegacyPackaging = true
|
||||
jniLibs.keepDebugSymbols += setOf("**/*.so")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user