From d726dbc7803b9ec75f642ec841e74838e1c35c16 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Thu, 2 Apr 2026 22:30:41 -0400 Subject: [PATCH] sloppy stuff --- accel/tcg/backend-ldst.h | 8 +- accel/tcg/cpu-exec.c | 332 ++++ accel/tcg/tb-cache-hints.c | 641 ++++++++ accel/tcg/tb-cache-hints.h | 153 ++ accel/tcg/tb-maint.c | 35 + accel/tcg/tcg-accel-ops-mttcg.c | 13 + accel/tcg/translate-all.c | 559 ++++++- accel/tcg/translator.c | 14 + android/app/build.gradle.kts | 2 + android/app/src/main/cpp/CMakeLists.txt | 10 + android/app/src/main/cpp/fast_hash_stub.c | 75 + android/app/src/main/cpp/hakux_stubs.c | 7 + android/app/src/main/cpp/nv2a_vsh_emulator.h | 48 + .../app/src/main/cpp/nv2a_vsh_emulator_stub.c | 36 + android/app/src/main/cpp/samplerate.h | 32 + android/app/src/main/cpp/samplerate_stub.c | 209 +++ android/app/src/main/cpp/xemu_android.cpp | 366 ++++- .../app/src/main/cpp/xemu_settings_android.cc | 92 +- .../izzy2lost/x1box/FrontendLaunchHelper.kt | 2 +- .../com/izzy2lost/x1box/GpuDriverHelper.kt | 181 +++ .../java/com/izzy2lost/x1box/MainActivity.kt | 120 +- .../x1box/PerGameSettingsActivity.kt | 2 +- .../com/izzy2lost/x1box/SettingsActivity.kt | 336 ++--- .../src/main/res/layout/activity_settings.xml | 49 +- android/app/src/main/res/values/strings.xml | 22 +- config_spec.yml | 2 +- hw/xbox/nv2a/pgraph/gl/constants.h | 47 +- hw/xbox/nv2a/pgraph/gl/display.c | 33 +- hw/xbox/nv2a/pgraph/gl/draw.c | 45 +- hw/xbox/nv2a/pgraph/gl/renderer.c | 64 +- hw/xbox/nv2a/pgraph/gl/renderer.h | 24 - hw/xbox/nv2a/pgraph/gl/reports.c | 8 - hw/xbox/nv2a/pgraph/gl/shaders.c | 99 +- hw/xbox/nv2a/pgraph/gl/surface.c | 1192 +-------------- hw/xbox/nv2a/pgraph/gl/texture.c | 413 +---- hw/xbox/nv2a/pgraph/gl/vertex.c | 23 +- hw/xbox/nv2a/pgraph/glsl/geom.c | 6 +- hw/xbox/nv2a/pgraph/glsl/psh.c | 137 +- hw/xbox/nv2a/pgraph/glsl/psh.h | 4 - hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 18 +- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 42 +- hw/xbox/nv2a/pgraph/glsl/vsh.c | 40 +- hw/xbox/nv2a/pgraph/glsl/vsh.h | 2 - hw/xbox/nv2a/pgraph/pgraph.c | 261 +--- hw/xbox/nv2a/pgraph/pgraph.h | 42 +- hw/xbox/nv2a/pgraph/profile.c | 10 - hw/xbox/nv2a/pgraph/rdi.c | 3 - hw/xbox/nv2a/pgraph/swizzle.c | 6 - hw/xbox/nv2a/pgraph/texture.h | 28 - hw/xbox/nv2a/pgraph/vk/blit.c | 8 + hw/xbox/nv2a/pgraph/vk/buffer.c | 118 +- hw/xbox/nv2a/pgraph/vk/compile_worker.c | 240 --- hw/xbox/nv2a/pgraph/vk/draw.c | 1332 ++--------------- hw/xbox/nv2a/pgraph/vk/glsl.c | 183 +-- hw/xbox/nv2a/pgraph/vk/image.c | 33 - hw/xbox/nv2a/pgraph/vk/instance.c | 587 +------- hw/xbox/nv2a/pgraph/vk/meson.build | 1 - hw/xbox/nv2a/pgraph/vk/renderer.c | 26 +- hw/xbox/nv2a/pgraph/vk/renderer.h | 249 +-- hw/xbox/nv2a/pgraph/vk/shaders.c | 576 +------ hw/xbox/nv2a/pgraph/vk/surface-compute.c | 329 +--- hw/xbox/nv2a/pgraph/vk/surface.c | 420 +----- hw/xbox/nv2a/pgraph/vk/texture.c | 669 ++------- implementation_plan.md | 43 + include/exec/translation-block.h | 41 + include/exec/translator.h | 1 + include/tcg/tcg.h | 37 + include/xemu-config.h | 3 +- roms/ipxe | 2 +- roms/openbios | 2 +- roms/opensbi | 2 +- roms/seabios | 2 +- roms/seabios-hppa | 2 +- roms/skiboot | 2 +- roms/vbootrom | 2 +- system/physmem.c | 18 + target/i386/cpu.h | 2 + target/i386/helper.h | 2 +- target/i386/ops_fpu.h | 117 +- target/i386/ops_sse.h | 796 +++++++++- target/i386/tcg/emit.c.inc | 42 + target/i386/tcg/fpu_helper.c | 493 +++++- target/i386/tcg/fpu_helper_hard.c | 2 +- target/i386/tcg/translate.c | 254 +++- tcg/aarch64/tcg-target-con-set.h | 2 + tcg/aarch64/tcg-target-has.h | 2 + tcg/aarch64/tcg-target.c.inc | 870 ++++++++++- tcg/optimize.c | 41 + tcg/region.c | 17 + tcg/tcg-internal.h | 11 + tcg/tcg-op.c | 8 +- tcg/tcg.c | 46 + tcg/tier1-opt.c | 378 +++++ tests/lcitool/libvirt-ci | 2 +- ui/xemu.c | 241 ++- ui/xui/main-menu.cc | 4 +- 96 files changed, 6841 insertions(+), 7310 deletions(-) create mode 100644 accel/tcg/tb-cache-hints.c create mode 100644 accel/tcg/tb-cache-hints.h create mode 100644 android/app/src/main/cpp/fast_hash_stub.c create mode 100644 android/app/src/main/cpp/hakux_stubs.c create mode 100644 android/app/src/main/cpp/nv2a_vsh_emulator.h create mode 100644 android/app/src/main/cpp/nv2a_vsh_emulator_stub.c create mode 100644 android/app/src/main/cpp/samplerate.h create mode 100644 android/app/src/main/cpp/samplerate_stub.c create mode 100644 android/app/src/main/java/com/izzy2lost/x1box/GpuDriverHelper.kt delete mode 100644 hw/xbox/nv2a/pgraph/vk/compile_worker.c create mode 100644 implementation_plan.md create mode 100644 tcg/tier1-opt.c diff --git a/accel/tcg/backend-ldst.h b/accel/tcg/backend-ldst.h index d673a38407..ca9c2b3687 100644 --- a/accel/tcg/backend-ldst.h +++ b/accel/tcg/backend-ldst.h @@ -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(); \ diff --git a/accel/tcg/cpu-exec.c b/accel/tcg/cpu-exec.c index d93cd1734d..c66ade2125 100644 --- a/accel/tcg/cpu-exec.c +++ b/accel/tcg/cpu-exec.c @@ -47,6 +47,279 @@ #include "tb-context.h" #include "tb-internal.h" #include "internal-common.h" +#include "tb-cache-hints.h" +#ifdef __ANDROID__ +#include +#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; diff --git a/accel/tcg/tb-cache-hints.c b/accel/tcg/tb-cache-hints.c new file mode 100644 index 0000000000..39f2952e87 --- /dev/null +++ b/accel/tcg/tb-cache-hints.c @@ -0,0 +1,641 @@ +/* + * Persistent TCG Translation Block Cache Hints + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#ifndef XEMU_OPT_TB_CACHE_HINTS +#define XEMU_OPT_TB_CACHE_HINTS 1 +#endif + +#if defined(XBOX) && XEMU_OPT_TB_CACHE_HINTS + +#include "tb-cache-hints.h" +#include "exec/translation-block.h" +#include "accel/tcg/tb-cpu-state.h" +#include "accel/tcg/cpu-ops.h" +#include "exec/mmap-lock.h" +#include "exec/cpu-common.h" +#include "internal-common.h" +#include "qemu/log.h" +#include "qemu/crc32c.h" +#include +#ifdef __ANDROID__ +#include +#endif + +/* ------------------------------------------------------------------ */ +/* File format */ +/* ------------------------------------------------------------------ */ + +#define TB_CACHE_MAGIC 0x54424843 /* "TBCH" */ +#define TB_CACHE_VERSION 4 + +typedef struct TBCacheFileHeader { + uint32_t magic; + uint32_t version; + uint32_t game_hash; + uint32_t hint_count; + uint32_t build_hash; /* v4: auto-invalidate on new builds */ + uint32_t reserved[3]; +} TBCacheFileHeader; + +/* Compile-time build fingerprint so the cache auto-invalidates when + * code generation changes (new build, different compiler flags, etc.) */ +static uint32_t tb_cache_build_hash(void) +{ + const char *stamp = __DATE__ " " __TIME__; + uint32_t h = 5381; + for (const char *p = stamp; *p; p++) { + h = ((h << 5) + h) ^ (uint8_t)*p; + } + return h; +} + +/* v1 hint struct for backward-compatible loading of old cache files. */ +typedef struct TBCacheHintV1 { + uint64_t pc; + uint64_t cs_base; + uint32_t flags; + uint32_t cflags; + uint64_t phys_pc; +} TBCacheHintV1; + +/* v2 hint struct (no superblock fields). */ +typedef struct TBCacheHintV2 { + uint64_t pc; + uint64_t cs_base; + uint32_t flags; + uint32_t cflags; + uint64_t phys_pc; + uint32_t exec_count; + uint8_t tier; + uint8_t pad[3]; +} TBCacheHintV2; + +/* ------------------------------------------------------------------ */ +/* Runtime state */ +/* ------------------------------------------------------------------ */ + +#define TB_CACHE_MAX_HINTS 65536 +#define TB_CACHE_HASH_BUCKETS 8192 +#define TB_CACHE_HASH_MASK (TB_CACHE_HASH_BUCKETS - 1) + +/* + * Simple open-addressing hash set used for deduplication during + * recording. Keyed on (pc ^ flags). + */ +static TBCacheHint *recorded_hints; +static int recorded_count; +static int recorded_capacity; + +/* Dedup hash set -- stores indices+1 into recorded_hints (0 = empty). */ +static uint32_t *dedup_table; + +/* Loaded hints ready for pre-warming. */ +static TBCacheHint *loaded_hints; +static int loaded_count; +static char *save_target_path; +static uint32_t save_target_game_hash; +static int last_autosave_count; + +/* ------------------------------------------------------------------ */ +/* Runtime metrics */ +/* ------------------------------------------------------------------ */ + +/* These are declared extern in tb-cache-hints.h for inline accessors. */ +uint64_t tb_cache_stats_lookup_hits; +uint64_t tb_cache_stats_lookup_misses; +uint64_t tb_cache_stats_call_count; + +static uint64_t stats_prev_hits; +static uint64_t stats_prev_misses; +static int stats_prewarmed_count; + +/* ------------------------------------------------------------------ */ +/* Dedup helpers */ +/* ------------------------------------------------------------------ */ + +static uint32_t hint_hash(uint64_t pc, uint32_t flags) +{ + uint64_t h = pc ^ ((uint64_t)flags << 17) ^ (pc >> 23); + h ^= h >> 16; + return (uint32_t)h & TB_CACHE_HASH_MASK; +} + +static bool hint_eq(const TBCacheHint *a, const TBCacheHint *b) +{ + return a->pc == b->pc + && a->cs_base == b->cs_base + && a->flags == b->flags; +} + +/* Returns true if the hint was already present. */ +static bool dedup_contains_or_insert(const TBCacheHint *h, int idx) +{ + if (!dedup_table) { + return false; + } + uint32_t bucket = hint_hash(h->pc, h->flags); + for (int probe = 0; probe < 16; probe++) { + uint32_t slot = (bucket + probe) & TB_CACHE_HASH_MASK; + uint32_t val = dedup_table[slot]; + if (val == 0) { + dedup_table[slot] = (uint32_t)(idx + 1); + return false; + } + if (hint_eq(&recorded_hints[val - 1], h)) { + return true; /* duplicate */ + } + } + /* Hash table is too full; accept the duplicate rather than resize. */ + return false; +} + +void tb_cache_set_save_target(const char *path, uint32_t game_hash) +{ + g_free(save_target_path); + save_target_path = NULL; + save_target_game_hash = game_hash; + last_autosave_count = 0; + + if (path && path[0] != '\0') { + save_target_path = g_strdup(path); + } +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void tb_cache_record_hint(const TranslationBlock *tb) +{ + /* Skip one-shot TBs and invalid entries. */ + if (tb_page_addr0(tb) == (tb_page_addr_t)-1) { + return; + } + if (tb->cflags & CF_INVALID) { + return; + } + + /* Lazy initialisation. */ + if (!recorded_hints) { + recorded_capacity = 4096; + recorded_hints = g_new(TBCacheHint, recorded_capacity); + dedup_table = g_new0(uint32_t, TB_CACHE_HASH_BUCKETS); + recorded_count = 0; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "hakuX-tb", + "recording started (max %d hints)", + TB_CACHE_MAX_HINTS); +#endif + } + + if (recorded_count >= TB_CACHE_MAX_HINTS) { + return; /* cap reached */ + } + + TBCacheHint h = { + .pc = tb->pc, + .cs_base = tb->cs_base, + .flags = tb->flags, + .cflags = tb->cflags & ~(CF_COUNT_MASK | CF_INVALID | CF_TIER1 | CF_SUPERBLOCK), + .phys_pc = tb_page_addr0(tb), + .exec_count = tb->exec_count, + .tier = tb->tier, + .is_superblock = (tb->superblock != NULL) ? 1 : 0, + .pc_b = (tb->superblock != NULL) ? tb->superblock->pc_b : 0, + }; + + /* + * On dedup collision, update exec_count/tier to max so the hottest + * observation is preserved across re-translations. + */ + if (dedup_contains_or_insert(&h, recorded_count)) { + /* Find the existing hint and update its hotness data. */ + uint32_t bucket = hint_hash(h.pc, h.flags); + for (int probe = 0; probe < 16; probe++) { + uint32_t slot = (bucket + probe) & TB_CACHE_HASH_MASK; + uint32_t val = dedup_table ? dedup_table[slot] : 0; + if (val && hint_eq(&recorded_hints[val - 1], &h)) { + TBCacheHint *existing = &recorded_hints[val - 1]; + if (h.exec_count > existing->exec_count) { + existing->exec_count = h.exec_count; + } + if (h.tier > existing->tier) { + existing->tier = h.tier; + } + break; + } + } + return; + } + + /* Grow array if needed. */ + if (recorded_count >= recorded_capacity) { + recorded_capacity = MIN(recorded_capacity * 2, TB_CACHE_MAX_HINTS); + recorded_hints = g_renew(TBCacheHint, recorded_hints, recorded_capacity); + } + + recorded_hints[recorded_count++] = h; + + if (save_target_path && recorded_count >= 100 && + recorded_count - last_autosave_count >= 256) { + tb_cache_save(save_target_path, save_target_game_hash); + last_autosave_count = recorded_count; + } +} + +void tb_cache_save(const char *path, uint32_t game_hash) +{ + if (!recorded_hints || recorded_count < 100) { + qemu_log("tb_cache_save: too few hints (%d), skipping\n", + recorded_count); + return; + } + + FILE *f = fopen(path, "wb"); + if (!f) { + qemu_log("tb_cache_save: cannot open %s for writing\n", path); + return; + } + + TBCacheFileHeader hdr = { + .magic = TB_CACHE_MAGIC, + .version = TB_CACHE_VERSION, + .game_hash = game_hash, + .hint_count = (uint32_t)recorded_count, + .build_hash = tb_cache_build_hash(), + }; + + if (fwrite(&hdr, sizeof(hdr), 1, f) != 1) { + goto fail; + } + if (fwrite(recorded_hints, sizeof(TBCacheHint), + recorded_count, f) != (size_t)recorded_count) { + goto fail; + } + + fclose(f); + qemu_log("tb_cache_save: wrote %d hints to %s\n", recorded_count, path); + return; + +fail: + fclose(f); + remove(path); + qemu_log("tb_cache_save: write failed, removed %s\n", path); +} + +int tb_cache_load(const char *path, uint32_t game_hash) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return 0; + } + + TBCacheFileHeader hdr; + if (fread(&hdr, sizeof(hdr), 1, f) != 1) { + goto fail; + } + + if (hdr.magic != TB_CACHE_MAGIC) { + qemu_log("tb_cache_load: bad magic in %s\n", path); + goto fail; + } + if (hdr.version < 1 || hdr.version > TB_CACHE_VERSION) { + qemu_log("tb_cache_load: unsupported version %u in %s\n", + hdr.version, path); + goto fail; + } + if (hdr.game_hash != game_hash) { + qemu_log("tb_cache_load: game hash mismatch in %s\n", path); + goto fail; + } + if (hdr.version >= 4 && hdr.build_hash != tb_cache_build_hash()) { + qemu_log("tb_cache_load: build hash mismatch (cache=0x%08x current=0x%08x) in %s\n", + hdr.build_hash, tb_cache_build_hash(), path); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_WARN, "hakuX-tb", + "TB cache rejected: built by different build"); +#endif + goto fail; + } + if (hdr.hint_count == 0 || hdr.hint_count > TB_CACHE_MAX_HINTS) { + qemu_log("tb_cache_load: bad hint_count %u in %s\n", + hdr.hint_count, path); + goto fail; + } + + loaded_hints = g_new(TBCacheHint, hdr.hint_count); + + if (hdr.version == 1) { + /* Read v1 hints and convert to v3. */ + TBCacheHintV1 *v1 = g_new(TBCacheHintV1, hdr.hint_count); + if (fread(v1, sizeof(TBCacheHintV1), + hdr.hint_count, f) != hdr.hint_count) { + g_free(v1); + g_free(loaded_hints); + loaded_hints = NULL; + goto fail; + } + for (uint32_t i = 0; i < hdr.hint_count; i++) { + memset(&loaded_hints[i], 0, sizeof(TBCacheHint)); + loaded_hints[i].pc = v1[i].pc; + loaded_hints[i].cs_base = v1[i].cs_base; + loaded_hints[i].flags = v1[i].flags; + loaded_hints[i].cflags = v1[i].cflags; + loaded_hints[i].phys_pc = v1[i].phys_pc; + } + g_free(v1); + qemu_log("tb_cache_load: upgraded %u v1 hints from %s\n", + hdr.hint_count, path); + } else if (hdr.version == 2) { + /* Read v2 hints (no superblock fields) and convert to v3. */ + TBCacheHintV2 *v2 = g_new(TBCacheHintV2, hdr.hint_count); + if (fread(v2, sizeof(TBCacheHintV2), + hdr.hint_count, f) != hdr.hint_count) { + g_free(v2); + g_free(loaded_hints); + loaded_hints = NULL; + goto fail; + } + for (uint32_t i = 0; i < hdr.hint_count; i++) { + memset(&loaded_hints[i], 0, sizeof(TBCacheHint)); + loaded_hints[i].pc = v2[i].pc; + loaded_hints[i].cs_base = v2[i].cs_base; + loaded_hints[i].flags = v2[i].flags; + loaded_hints[i].cflags = v2[i].cflags; + loaded_hints[i].phys_pc = v2[i].phys_pc; + loaded_hints[i].exec_count = v2[i].exec_count; + loaded_hints[i].tier = v2[i].tier; + } + g_free(v2); + qemu_log("tb_cache_load: upgraded %u v2 hints from %s\n", + hdr.hint_count, path); + } else { + /* v3: read directly. */ + if (fread(loaded_hints, sizeof(TBCacheHint), + hdr.hint_count, f) != hdr.hint_count) { + g_free(loaded_hints); + loaded_hints = NULL; + goto fail; + } + } + + loaded_count = (int)hdr.hint_count; + fclose(f); + qemu_log("tb_cache_load: loaded %d hints (v%u) from %s\n", + loaded_count, hdr.version, path); + return loaded_count; + +fail: + fclose(f); + return 0; +} + +/* Comparator: sort Tier 1 hints before Tier 0 for pre-warming priority. */ +static int hint_tier_cmp(const void *a, const void *b) +{ + const TBCacheHint *ha = a; + const TBCacheHint *hb = b; + /* Higher tier first, then higher exec_count first. */ + if (ha->tier != hb->tier) { + return (int)hb->tier - (int)ha->tier; + } + if (ha->exec_count != hb->exec_count) { + return (ha->exec_count > hb->exec_count) ? -1 : 1; + } + return 0; +} + +void tb_cache_prewarm(CPUState *cpu) +{ + if (!loaded_hints || loaded_count == 0) { + return; + } + + /* Sort: Tier 1 (hot) hints first, then by exec_count descending. */ + qsort(loaded_hints, loaded_count, sizeof(TBCacheHint), hint_tier_cmp); + + int translated = 0; + int tier1_count = 0; + int skipped = 0; + +#ifdef __ANDROID__ + /* On Android, skip prewarm — tb_gen_code can assert on hints where + * the translator's page state doesn't match the current memory layout. + * Blocks translate on-demand instead. The hints are still saved for + * tier tracking (exec_count, superblock formation). */ + __android_log_print(ANDROID_LOG_INFO, "hakuX-tb", + "prewarm: skipped (%d hints for on-demand use)", + loaded_count); + return; +#endif + + qemu_log("tb_cache_prewarm: pre-translating %d blocks...\n", loaded_count); + + for (int i = 0; i < loaded_count; i++) { + const TBCacheHint *h = &loaded_hints[i]; + + TCGTBCPUState s = { + .pc = (vaddr)h->pc, + .cs_base = h->cs_base, + .flags = h->flags, + .cflags = h->cflags | (h->tier >= 1 ? CF_TIER1 : 0), + }; + + mmap_lock(); + TranslationBlock *tb = tb_gen_code(cpu, s); + mmap_unlock(); + + if (tb) { + if (h->tier >= 1) { + /* Strip CF_TIER1 from stored cflags so lookup works. */ + tb->cflags &= ~CF_TIER1; + tb->tier = 1; + tb->exec_count = h->exec_count; + tier1_count++; + } + translated++; + } else { + skipped++; + } + } + + stats_prewarmed_count = translated; + + qemu_log("tb_cache_prewarm: translated %d (tier1=%d), skipped %d\n", + translated, tier1_count, skipped); + +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "hakuX-tb", + "prewarm: translated %d (tier1=%d), skipped %d", + translated, tier1_count, skipped); +#endif + + /* + * Note: Superblock hints (is_superblock=1) are not re-formed during + * prewarm because the component TBs may not yet have sufficient + * chain_count data. They will be re-formed naturally as the + * hot loops re-execute and the tier1_maybe_form_superblock() trigger + * fires. The component TBs (A and B) are already pre-warmed above. + */ + + /* Free loaded hints -- no longer needed. */ + g_free(loaded_hints); + loaded_hints = NULL; + loaded_count = 0; +} + +static uint32_t hash_file(const char *path, uint32_t crc) +{ + if (!path || path[0] == '\0') { + return crc; + } + FILE *f = fopen(path, "rb"); + if (!f) { + return crc; + } + uint8_t buf[4096]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) { + crc = crc32c(crc, buf, (unsigned int)n); + } + fclose(f); + return crc; +} + +uint32_t tb_cache_compute_game_hash(const char *bootrom_path, + const char *flashrom_path) +{ + uint32_t crc = 0xFFFFFFFF; + crc = hash_file(bootrom_path, crc); + crc = hash_file(flashrom_path, crc); + return crc; +} + +/* + * Slow path for periodic logging -- called from the inline + * tb_cache_maybe_log_stats() wrapper in tb-cache-hints.h when the + * call counter wraps around. + */ +void tb_cache_do_log_stats(void) +{ + tb_cache_stats_call_count = 0; + + uint64_t delta_hits = tb_cache_stats_lookup_hits - stats_prev_hits; + uint64_t delta_misses = tb_cache_stats_lookup_misses - stats_prev_misses; + uint64_t delta_total = delta_hits + delta_misses; + int hit_pct = delta_total > 0 + ? (int)(delta_hits * 100 / delta_total) + : 0; + +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "hakuX-tb", + "hits=%" PRIu64 " misses=%" PRIu64 + " rate=%d%% | total: recorded=%d" + " prewarmed=%d lookups=%" PRIu64, + delta_hits, delta_misses, hit_pct, + recorded_count, stats_prewarmed_count, + tb_cache_stats_lookup_hits + tb_cache_stats_lookup_misses); +#else + fprintf(stderr, + "[tb-cache] hits=%" PRIu64 " misses=%" PRIu64 + " rate=%d%% | total: hints_recorded=%d" + " hints_prewarmed=%d lookups=%" PRIu64 "\n", + delta_hits, delta_misses, hit_pct, + recorded_count, stats_prewarmed_count, + tb_cache_stats_lookup_hits + tb_cache_stats_lookup_misses); +#endif + + stats_prev_hits = tb_cache_stats_lookup_hits; + stats_prev_misses = tb_cache_stats_lookup_misses; +} + +static bool rewarm_in_progress; + +void tb_cache_rewarm_after_flush(CPUState *cpu) +{ + if (!recorded_hints || recorded_count == 0) { + return; + } + + /* Guard against recursive flush -> rewarm -> flush cycles. */ + if (rewarm_in_progress) { + return; + } + rewarm_in_progress = true; + + /* Sort hot-first so tier-1/high-exec_count TBs are placed contiguously + * at the start of the code buffer, improving L1I cache locality. */ + qsort(recorded_hints, recorded_count, sizeof(TBCacheHint), hint_tier_cmp); + + int translated = 0; + int tier1_count = 0; + int total = recorded_count; + + int skipped = 0; + for (int i = 0; i < total; i++) { + const TBCacheHint *h = &recorded_hints[i]; + + vaddr hint_pc = (vaddr)h->pc; + unsigned int page_offset = hint_pc & 0xFFF; /* 4KB page */ + if (page_offset > 0xFF0) { /* within 16 bytes of page end */ + skipped++; + continue; + } + + TCGTBCPUState s = { + .pc = hint_pc, + .cs_base = h->cs_base, + .flags = h->flags, + .cflags = h->cflags | (h->tier >= 1 ? CF_TIER1 : 0), + }; + + mmap_lock(); + TranslationBlock *tb = tb_gen_code(cpu, s); + mmap_unlock(); + + if (tb) { + if (h->tier >= 1) { + tb->cflags &= ~CF_TIER1; + tb->tier = 1; + tb->exec_count = h->exec_count; + tier1_count++; + } + translated++; + } + } + +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "hakuX-tb", + "post-flush rewarm: re-translated %d/%d blocks (tier1=%d)", + translated, total, tier1_count); +#endif + + rewarm_in_progress = false; +} + +void tb_cache_cleanup(void) +{ + g_free(recorded_hints); + recorded_hints = NULL; + recorded_count = 0; + recorded_capacity = 0; + + g_free(dedup_table); + dedup_table = NULL; + + g_free(loaded_hints); + loaded_hints = NULL; + loaded_count = 0; + + g_free(save_target_path); + save_target_path = NULL; + save_target_game_hash = 0; + last_autosave_count = 0; +} + +#endif /* XBOX && XEMU_OPT_TB_CACHE_HINTS */ diff --git a/accel/tcg/tb-cache-hints.h b/accel/tcg/tb-cache-hints.h new file mode 100644 index 0000000000..d808db2963 --- /dev/null +++ b/accel/tcg/tb-cache-hints.h @@ -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 */ diff --git a/accel/tcg/tb-maint.c b/accel/tcg/tb-maint.c index d21a8bd42f..d420c1d0e4 100644 --- a/accel/tcg/tb-maint.c +++ b/accel/tcg/tb-maint.c @@ -18,6 +18,9 @@ */ #include "qemu/osdep.h" +#ifdef __ANDROID__ +#include +#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) diff --git a/accel/tcg/tcg-accel-ops-mttcg.c b/accel/tcg/tcg-accel-ops-mttcg.c index cf1ee7ac25..4cd3cc0925 100644 --- a/accel/tcg/tcg-accel-ops-mttcg.c +++ b/accel/tcg/tcg-accel-ops-mttcg.c @@ -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); diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c index 0871388c6c..fae9d54e7d 100644 --- a/accel/tcg/translate-all.c +++ b/accel/tcg/translate-all.c @@ -38,6 +38,13 @@ #include "internal-common.h" #include "tcg/perf.h" #include "tcg/insn-start-words.h" +#include "tb-cache-hints.h" +#ifdef XBOX +#include "tb-code-hash.h" +#ifndef TCG_HIGHWATER +#define TCG_HIGHWATER 1024 +#endif +#endif #if defined(CONFIG_VTUNE_JITPROFILING) #include @@ -236,6 +243,15 @@ void page_init(void) page_table_config_init(); } +#ifdef XBOX +static bool tb_pc_cmp(const void *p, const void *d) +{ + const TranslationBlock *tb = p; + const vaddr *target = d; + return !(tb_cflags(tb) & CF_INVALID) && tb->pc == *target; +} +#endif + /* * Isolate the portion of code gen which can setjmp/longjmp. * Return the size of the generated code, or negative on error. @@ -259,6 +275,32 @@ static int setjmp_gen_code(CPUArchState *env, TranslationBlock *tb, tcg_ctx->cpu = NULL; *max_insns = tb->icount; +#ifdef XBOX + /* + * Populate successor CC defines for cross-TB dead flag elimination. + * jmp_target_addr is now set (translate_code just completed). + * Best-effort lookup of each successor in the TB hash table. + */ + tcg_ctx->succ_cc_defines[0] = 0; + tcg_ctx->succ_cc_defines[1] = 0; + if (tb_cflags(tb) & CF_TIER1) { + for (int n = 0; n < 2; n++) { + vaddr target = tb->jmp_target_addr[n]; + if (target == 0 || target == (vaddr)-1) { + continue; + } + uint32_t lc = tb_cflags(tb) & ~(CF_TIER1 | CF_INVALID); + uint32_t h = tb_hash_func(target, target, + tb->flags, tb->cs_base, lc); + TranslationBlock *succ = qht_lookup_custom( + &tb_ctx.htable, &target, h, tb_pc_cmp); + if (succ) { + tcg_ctx->succ_cc_defines[n] = succ->cc_defines_first; + } + } + } +#endif + return tcg_gen_code(tcg_ctx, tb, pc); } @@ -292,6 +334,24 @@ TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s) tb = inv_tb_htable_lookup(cpu, s); if (tb) { +#ifdef XBOX + /* + * If there is a pending tier-1 promotion request for this PC, + * do NOT recycle the old (tier-0) code. Remove the stale entry + * from inv_htable and fall through to fresh generation so the + * tier1_consume_request check triggers and CF_TIER1 is set. + */ + if (tier1_has_pending_request(s.pc, s.cs_base, s.flags)) { + uint32_t orig = tb_cflags(tb); + uint32_t h = tb_hash_func(phys_pc, + (orig & CF_PCREL ? 0 : tb->pc), + tb->flags, tb->cs_base, + orig & ~CF_INVALID); + qht_remove(&tb_ctx.inv_htable, tb, h); + tb = NULL; + goto skip_recycle; + } +#endif qemu_spin_lock(&tb->jmp_lock); qatomic_set(&tb->cflags, tb->cflags & ~CF_INVALID); qemu_spin_unlock(&tb->jmp_lock); @@ -308,9 +368,39 @@ TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s) recycled = true; goto recycle_tb; } +#ifdef XBOX + skip_recycle: +#endif + +#ifdef XBOX + bool hot_alloc = false; + void *saved_cgp = NULL, *saved_cgb = NULL, *saved_cghw = NULL; + size_t saved_cgbs = 0; +#endif buffer_overflow: assert_no_pages_locked(); + +#ifdef XBOX + if (!hot_alloc && (s.cflags & CF_TIER1) && tcg_ctx->hot_arena_start) { + tb = tcg_tb_alloc_hot(tcg_ctx); + if (tb) { + hot_alloc = true; + saved_cgp = tcg_ctx->code_gen_ptr; + saved_cgb = tcg_ctx->code_gen_buffer; + saved_cgbs = tcg_ctx->code_gen_buffer_size; + saved_cghw = tcg_ctx->code_gen_highwater; + tcg_ctx->code_gen_ptr = tcg_ctx->hot_arena_ptr; + tcg_ctx->code_gen_buffer = tcg_ctx->hot_arena_start; + tcg_ctx->code_gen_buffer_size = tcg_ctx->hot_arena_end + - tcg_ctx->hot_arena_start; + tcg_ctx->code_gen_highwater = tcg_ctx->hot_arena_end + - TCG_HIGHWATER; + goto got_tb; + } + } +#endif + tb = tcg_tb_alloc(tcg_ctx); if (unlikely(!tb)) { /* flush must be done */ @@ -326,6 +416,9 @@ TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s) cpu_loop_exit(cpu); } +#ifdef XBOX + got_tb: +#endif gen_code_buf = tcg_ctx->code_gen_ptr; tb->tc.ptr = tcg_splitwx_to_rx(gen_code_buf); if (!(s.cflags & CF_PCREL)) { @@ -334,6 +427,27 @@ TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s) tb->cs_base = s.cs_base; tb->flags = s.flags; tb->cflags = s.cflags; +#ifdef XBOX + tb->exec_count = 0; + tb->tier = 0; + tb->cc_defines_first = 0; + tb->chain_count[0] = 0; + tb->chain_count[1] = 0; + tb->superblock = NULL; + + /* Check if this PC has a pending tier-1 promotion request. */ + { + uint32_t tier1_cflags = tb->cflags; + int saved_exec = tier1_consume_request(s.pc, s.cs_base, s.flags, + &tier1_cflags); + if (saved_exec >= 0) { + tb->cflags = tier1_cflags; + s.cflags = tier1_cflags; + tb->tier = 1; + tb->exec_count = (uint32_t)saved_exec; + } + } +#endif tb_set_page_addr0(tb, phys_pc); tb_set_page_addr1(tb, -1); if (phys_pc != -1) { @@ -366,6 +480,15 @@ TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s) "code_gen_buffer overflow\n"); tb_unlock_pages(tb); tcg_ctx->gen_tb = NULL; +#ifdef XBOX + if (hot_alloc) { + tcg_ctx->code_gen_ptr = saved_cgp; + tcg_ctx->code_gen_buffer = saved_cgb; + tcg_ctx->code_gen_buffer_size = saved_cgbs; + tcg_ctx->code_gen_highwater = saved_cghw; + hot_alloc = false; + } +#endif goto buffer_overflow; case -2: @@ -501,9 +624,22 @@ TranslationBlock *tb_gen_code(CPUState *cpu, TCGTBCPUState s) } } - qatomic_set(&tcg_ctx->code_gen_ptr, (void *) - ROUND_UP((uintptr_t)gen_code_buf + gen_code_size + search_size, - CODE_GEN_ALIGN)); +#ifdef XBOX + if (hot_alloc) { + tcg_ctx->hot_arena_ptr = (void *) + ROUND_UP((uintptr_t)gen_code_buf + gen_code_size + search_size, + CODE_GEN_ALIGN); + tcg_ctx->code_gen_ptr = saved_cgp; + tcg_ctx->code_gen_buffer = saved_cgb; + tcg_ctx->code_gen_buffer_size = saved_cgbs; + tcg_ctx->code_gen_highwater = saved_cghw; + } else +#endif + { + qatomic_set(&tcg_ctx->code_gen_ptr, (void *) + ROUND_UP((uintptr_t)gen_code_buf + gen_code_size + search_size, + CODE_GEN_ALIGN)); + } /* init jump list */ qemu_spin_init(&tb->jmp_lock); @@ -549,6 +685,18 @@ recycle_tb: return tb; } +#ifdef XBOX + /* + * Strip CF_TIER1 BEFORE tb_link_page hashes the TB. CF_TIER1 is + * only used during code generation to trigger tier-1 optimisation + * passes; keeping it in cflags would place the TB in the wrong + * hash bucket, making it unfindable by normal lookups. + */ + if (tb->cflags & CF_TIER1) { + tb->cflags &= ~CF_TIER1; + } +#endif + /* * No explicit memory barrier is required -- tb_link_page() makes the * TB visible in a consistent state. @@ -562,12 +710,23 @@ recycle_tb: uintptr_t orig_aligned = (uintptr_t)gen_code_buf; orig_aligned -= ROUND_UP(sizeof(*tb), qemu_icache_linesize); - qatomic_set(&tcg_ctx->code_gen_ptr, (void *)orig_aligned); +#ifdef XBOX + if (hot_alloc) { + tcg_ctx->hot_arena_ptr = (void *)orig_aligned; + } else +#endif + { + qatomic_set(&tcg_ctx->code_gen_ptr, (void *)orig_aligned); + } } tcg_tb_remove(tb); return existing_tb; } +#ifdef XBOX + tb_cache_record_hint(tb); +#endif + #if defined(CONFIG_VTUNE_JITPROFILING) if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON && !recycled) { iJIT_Method_Load *jmethod = g_malloc0(sizeof(iJIT_Method_Load)); @@ -682,3 +841,395 @@ void tcg_flush_jmp_cache(CPUState *cpu) qatomic_set(&jc->array[i].tb, NULL); } } + +/* ================================================================== */ +/* Superblock Formation: merge TB A and TB B into a single TB */ +/* ================================================================== */ + +#ifdef XBOX + +#include "tcg/tcg-op-common.h" +#include "exec/translator.h" +#ifdef __ANDROID__ +#include +#define SB_LOG(...) __android_log_print(ANDROID_LOG_INFO, "superblock", __VA_ARGS__) +#else +#define SB_LOG(...) do {} while (0) +#endif + +/* + * IR surgery: find the goto_tb + exit_tb pair for a given exit slot. + * Walks backward from the end of the ops list. + * Returns the goto_tb op, or NULL if not found. + * Also sets *out_exit_tb to the corresponding exit_tb op. + */ +static TCGOp *sb_find_exit_ops(TCGContext *s, int slot, TCGOp **out_exit_tb) +{ + TCGOp *found_exit = NULL; + TCGOp *found_goto = NULL; + TCGOp *op; + + *out_exit_tb = NULL; + + QTAILQ_FOREACH_REVERSE(op, &s->ops, link) { + if (op->opc == INDEX_op_exit_tb) { + uintptr_t val = op->args[0]; + /* + * exit_tb encodes (tb_ptr | exit_idx). The bottom bits + * give the slot index. TB_EXIT_REQUESTED is a special + * value; skip it. + */ + int idx = val & 3; + if (val != 0 && idx == slot) { + found_exit = op; + /* The goto_tb for this slot should be shortly before. */ + TCGOp *prev = QTAILQ_PREV(op, link); + while (prev) { + if (prev->opc == INDEX_op_goto_tb && + (int)prev->args[0] == slot) { + found_goto = prev; + *out_exit_tb = found_exit; + return found_goto; + } + /* Don't search too far back. */ + if (prev->opc == INDEX_op_set_label || + prev->opc == INDEX_op_call) { + break; + } + prev = QTAILQ_PREV(prev, link); + } + } + } + } + return NULL; +} + +/* + * Remap an exit_tb op to use a new slot index and TB pointer. + * Also remap the corresponding goto_tb slot number. + */ +static void sb_remap_exit(TCGOp *goto_op, TCGOp *exit_op, + int new_slot, TranslationBlock *new_tb) +{ + goto_op->args[0] = new_slot; + exit_op->args[0] = (uintptr_t)new_tb | new_slot; +} + +/* + * Detach the trailing exitreq epilogue (set_label + exit_tb EXIT_REQUESTED) + * from the ops list. Save the ops for later re-attachment. + */ +static bool sb_detach_exitreq(TCGContext *s, + TCGOp **out_label, TCGOp **out_exit) +{ + *out_label = NULL; + *out_exit = NULL; + + /* The exitreq exit_tb should be the very last op. */ + TCGOp *last = QTAILQ_LAST(&s->ops); + if (!last || last->opc != INDEX_op_exit_tb) { + return false; + } + + /* The set_label should be just before it. */ + TCGOp *prev = QTAILQ_PREV(last, link); + if (!prev || prev->opc != INDEX_op_set_label) { + return false; + } + + *out_exit = last; + *out_label = prev; + + QTAILQ_REMOVE(&s->ops, last, link); + s->nb_ops--; + QTAILQ_REMOVE(&s->ops, prev, link); + s->nb_ops--; + + return true; +} + +/* + * Re-attach the exitreq epilogue at the tail, updating the TB pointer. + */ +static void sb_reattach_exitreq(TCGContext *s, + TCGOp *label_op, TCGOp *exit_op, + TranslationBlock *sb) +{ + /* Update the exit_tb arg to point to the superblock. */ + exit_op->args[0] = (uintptr_t)sb | TB_EXIT_REQUESTED; + + QTAILQ_INSERT_TAIL(&s->ops, label_op, link); + s->nb_ops++; + QTAILQ_INSERT_TAIL(&s->ops, exit_op, link); + s->nb_ops++; +} + +/* + * tb_gen_superblock -- merge TB A and TB B into a single superblock. + * + * This is the main formation function. It: + * 1. Translates A's guest code into TCG IR + * 2. Performs IR surgery to remove A's dominant exit + * 3. Translates B's guest code, appending to the same IR + * 4. Remaps exit slots and reattaches the exitreq epilogue + * 5. Generates native code via tcg_gen_code() + * + * Returns the new superblock TB, or NULL on failure. + */ +TranslationBlock *tb_gen_superblock(CPUState *cpu, + TranslationBlock *tb_a, + int dominant_exit) +{ + CPUArchState *env = cpu_env(cpu); + TranslationBlock *tb, *existing_tb; + tb_page_addr_t phys_pc_a, phys_pc_b; + void *host_pc_a, *host_pc_b; + tcg_insn_unit *gen_code_buf; + int gen_code_size, search_size, max_insns; + int64_t ti; + int non_dominant = 1 - dominant_exit; + + /* Look up TB B from A's jump destination. */ + uintptr_t dest = qatomic_read(&tb_a->jmp_dest[dominant_exit]); + if (dest == (uintptr_t)NULL || (dest & 1)) { + return NULL; + } + TranslationBlock *tb_b = (TranslationBlock *)dest; + + /* Resolve physical addresses and host pointers. */ + phys_pc_a = get_page_addr_code_hostp(env, tb_a->pc, &host_pc_a); + phys_pc_b = get_page_addr_code_hostp(env, tb_b->pc, &host_pc_b); + if (phys_pc_a == -1 || phys_pc_b == -1) { + return NULL; + } + + /* + * Invalidate the original TB A before creating the superblock. + * Pass -1 so the TB is properly removed from the page list. + */ + tb_phys_invalidate(tb_a, -1); + + assert_memory_lock(); + qemu_thread_jit_write(); + + /* Allocate a new TB for the superblock. */ + tb = tcg_tb_alloc(tcg_ctx); + if (!tb) { + return NULL; + } + + gen_code_buf = tcg_ctx->code_gen_ptr; + tb->tc.ptr = tcg_splitwx_to_rx(gen_code_buf); + tb->pc = tb_a->pc; + tb->cs_base = tb_a->cs_base; + tb->flags = tb_a->flags; + tb->cflags = (tb_a->cflags & ~(CF_COUNT_MASK | CF_INVALID | CF_TIER1)) + | CF_TIER1 | CF_SUPERBLOCK; + tb->exec_count = 0; + tb->tier = 2; + tb->chain_count[0] = 0; + tb->chain_count[1] = 0; + tb->superblock = NULL; + tb_set_page_addr0(tb, phys_pc_a); + tb_set_page_addr1(tb, (phys_pc_a != phys_pc_b) ? phys_pc_b : -1); + tb_lock_page0(phys_pc_a); + if (phys_pc_a != phys_pc_b) { + tb_lock_page1(phys_pc_a, phys_pc_b); + } + + tcg_ctx->gen_tb = tb; + tcg_ctx->addr_type = target_long_bits() == 32 ? TCG_TYPE_I32 : TCG_TYPE_I64; + tcg_ctx->guest_mo = cpu->cc->tcg_ops->guest_default_memory_order; + + /* Step 1: Translate A's instruction range (standard path). */ + int ret = sigsetjmp(tcg_ctx->jmp_trans, 0); + if (ret != 0) { + /* Translation error -- bail out. */ + tb_unlock_pages(tb); + tcg_ctx->gen_tb = NULL; + return NULL; + } + + tcg_func_start(tcg_ctx); + tcg_ctx->cpu = cpu; + + max_insns = tb_a->icount; + if (max_insns == 0) { + max_insns = TCG_MAX_INSNS; + } + + cpu->cc->tcg_ops->translate_code(cpu, tb, &max_insns, tb_a->pc, host_pc_a); + + int a_insns = tb->icount; + int a_size = tb->size; + + /* Step 2: Detach the exitreq epilogue. */ + TCGOp *exitreq_label, *exitreq_exit; + if (!sb_detach_exitreq(tcg_ctx, &exitreq_label, &exitreq_exit)) { + tb_unlock_pages(tb); + tcg_ctx->gen_tb = NULL; + tcg_ctx->cpu = NULL; + return NULL; + } + + /* Step 3: Find and remove the dominant exit (goto_tb + exit_tb). */ + TCGOp *dom_goto, *dom_exit; + dom_goto = sb_find_exit_ops(tcg_ctx, dominant_exit, &dom_exit); + if (!dom_goto || !dom_exit) { + /* Can't find the exit -- reattach exitreq and bail. */ + sb_reattach_exitreq(tcg_ctx, exitreq_label, exitreq_exit, tb); + tb_unlock_pages(tb); + tcg_ctx->gen_tb = NULL; + tcg_ctx->cpu = NULL; + return NULL; + } + + /* Remove the dominant exit ops. */ + tcg_op_remove(tcg_ctx, dom_goto); + tcg_op_remove(tcg_ctx, dom_exit); + + /* Step 4: Remap non-dominant exit to slot 0. */ + TCGOp *nd_goto, *nd_exit; + nd_goto = sb_find_exit_ops(tcg_ctx, non_dominant, &nd_exit); + if (nd_goto && nd_exit) { + sb_remap_exit(nd_goto, nd_exit, 0, tb); + } + + /* Step 5: Translate B's instructions (appended to existing IR). + * Set superblock_append so translator_loop skips gen_tb_start/gen_tb_end. */ + int b_max = tb_b->icount; + if (b_max == 0) { + b_max = TCG_MAX_INSNS; + } + + tcg_ctx->superblock_append = true; +#ifdef CONFIG_DEBUG_TCG + tcg_ctx->goto_tb_issue_mask = 0; +#endif + cpu->cc->tcg_ops->translate_code(cpu, tb, &b_max, tb_b->pc, host_pc_b); + tcg_ctx->superblock_append = false; + + int b_insns = tb->icount; /* translate_code updates tb->icount */ + /* tb->size was updated by translate_code to B's size; save it. */ + int b_size = tb->size; + + /* Step 6: Remap B's exits. + * IMPORTANT: Remap slot 1 first, then slot 0, to avoid finding + * a just-remapped op when searching. + * + * B's exit slot 1 -> remove goto_tb, convert to indirect lookup + * B's exit slot 0 -> superblock slot 1 + */ + TCGOp *b_goto1, *b_exit1; + b_goto1 = sb_find_exit_ops(tcg_ctx, 1, &b_exit1); + if (b_goto1 && b_exit1) { + /* + * Convert B's second exit to an indirect lookup. + * Remove goto_tb, keep exit_tb with val=0 (triggers epilogue + * return with NULL, which the main loop handles as a full lookup). + */ + tcg_op_remove(tcg_ctx, b_goto1); + b_exit1->args[0] = 0; /* exit_tb(NULL, 0) -> full lookup */ + } + + TCGOp *b_goto0, *b_exit0; + b_goto0 = sb_find_exit_ops(tcg_ctx, 0, &b_exit0); + if (b_goto0 && b_exit0) { + sb_remap_exit(b_goto0, b_exit0, 1, tb); + } + + /* Step 7: Reattach exitreq epilogue. */ + sb_reattach_exitreq(tcg_ctx, exitreq_label, exitreq_exit, tb); + + /* Step 8: Generate native code (Tier 1 passes apply automatically). */ + tb->size = a_size; /* Restore A's size for the TB entry point range */ + tb->icount = a_insns + b_insns; + (void)b_size; /* b_size tracked in SuperblockInfo */ + + gen_code_size = tcg_gen_code(tcg_ctx, tb, tb_a->pc); + tcg_ctx->cpu = NULL; + tcg_ctx->gen_tb = NULL; + + if (gen_code_size < 0) { + /* Code generation failed -- clean up. */ + tb_unlock_pages(tb); + return NULL; + } + + search_size = encode_search(tb, (void *)gen_code_buf + gen_code_size); + if (search_size < 0) { + tb_unlock_pages(tb); + tcg_ctx->gen_tb = NULL; + return NULL; + } + tb->tc.size = gen_code_size; + + qatomic_set(&tcg_ctx->code_gen_ptr, (void *) + ROUND_UP((uintptr_t)gen_code_buf + gen_code_size + search_size, + CODE_GEN_ALIGN)); + + /* Init jump list. */ + qemu_spin_init(&tb->jmp_lock); + tb->jmp_list_head = (uintptr_t)NULL; + tb->jmp_list_next[0] = (uintptr_t)NULL; + tb->jmp_list_next[1] = (uintptr_t)NULL; + tb->jmp_dest[0] = (uintptr_t)NULL; + tb->jmp_dest[1] = (uintptr_t)NULL; + + if (tb->jmp_reset_offset[0] != TB_JMP_OFFSET_INVALID) { + tb_reset_jump(tb, 0); + } + if (tb->jmp_reset_offset[1] != TB_JMP_OFFSET_INVALID) { + tb_reset_jump(tb, 1); + } + + /* + * Compute ihash over the superblock's guest code range (A's code). + * This allows proper TB recycling if the superblock is later + * invalidated and re-requested. + */ + tb->ihash = tb_code_hash_func(env, tb->pc, tb->size); + + /* + * Strip CF_TIER1 | CF_SUPERBLOCK BEFORE insertion so the QHT hash + * matches what normal lookups compute (they never include these flags). + * tcg_gen_code already used CF_TIER1 for Tier 1 optimization passes. + */ + tb->cflags &= ~(CF_TIER1 | CF_SUPERBLOCK); + tb->tier = 2; + + tcg_tb_insert(tb); + + if (tb_page_addr0(tb) == -1) { + assert_no_pages_locked(); + goto fill_superblock_info; + } + + existing_tb = tb_link_page(tb); + assert_no_pages_locked(); + + if (existing_tb != tb) { + tcg_tb_remove(tb); + return NULL; + } + +fill_superblock_info: + ; /* empty statement to satisfy C99 label-before-declaration rule */ + /* Step 9: Fill in SuperblockInfo. */ + SuperblockInfo *sbi = g_malloc0(sizeof(SuperblockInfo)); + sbi->pc_b = tb_b->pc; + sbi->size_b = tb_b->size; + sbi->icount_b = b_insns; + sbi->phys_pc_b = phys_pc_b; + tb->superblock = sbi; + + SB_LOG("formed A=0x%" PRIx64 " + B=0x%" PRIx64 + ", combined %d insns, pages=%s", + (uint64_t)tb_a->pc, (uint64_t)tb_b->pc, + a_insns + b_insns, + (phys_pc_a != phys_pc_b) ? "2" : "1"); + + return tb; +} + +#endif /* XBOX */ diff --git a/accel/tcg/translator.c b/accel/tcg/translator.c index 2fa8c48485..5979874eed 100644 --- a/accel/tcg/translator.c +++ b/accel/tcg/translator.c @@ -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 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 24a717b479..864f1669f7 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -101,6 +101,8 @@ android { "META-INF/LICENSE*", "META-INF/NOTICE*" ) + jniLibs.useLegacyPackaging = true + jniLibs.keepDebugSymbols += setOf("**/*.so") } compileOptions { diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index ae33204d2d..840d42086e 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -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} $<$:${vulkan-lib}> $<$:volk_static> + $<$:adrenotools> $<$:spirv_reflect_static> $<$:glslang> $<$:MachineIndependent> diff --git a/android/app/src/main/cpp/fast_hash_stub.c b/android/app/src/main/cpp/fast_hash_stub.c new file mode 100644 index 0000000000..ce645e6c08 --- /dev/null +++ b/android/app/src/main/cpp/fast_hash_stub.c @@ -0,0 +1,75 @@ +#include "qemu/osdep.h" +#include "qemu/fast-hash.h" + +#ifdef __aarch64__ +#include + +__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 diff --git a/android/app/src/main/cpp/hakux_stubs.c b/android/app/src/main/cpp/hakux_stubs.c new file mode 100644 index 0000000000..e51dac7991 --- /dev/null +++ b/android/app/src/main/cpp/hakux_stubs.c @@ -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. + */ diff --git a/android/app/src/main/cpp/nv2a_vsh_emulator.h b/android/app/src/main/cpp/nv2a_vsh_emulator.h new file mode 100644 index 0000000000..4792b6fa92 --- /dev/null +++ b/android/app/src/main/cpp/nv2a_vsh_emulator.h @@ -0,0 +1,48 @@ +#ifndef NV2A_VSH_EMULATOR_H +#define NV2A_VSH_EMULATOR_H + +#include +#include + +#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 diff --git a/android/app/src/main/cpp/nv2a_vsh_emulator_stub.c b/android/app/src/main/cpp/nv2a_vsh_emulator_stub.c new file mode 100644 index 0000000000..d06d142ee9 --- /dev/null +++ b/android/app/src/main/cpp/nv2a_vsh_emulator_stub.c @@ -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; +} diff --git a/android/app/src/main/cpp/samplerate.h b/android/app/src/main/cpp/samplerate.h new file mode 100644 index 0000000000..e07166891a --- /dev/null +++ b/android/app/src/main/cpp/samplerate.h @@ -0,0 +1,32 @@ +#ifndef SAMPLERATE_H +#define SAMPLERATE_H + +#include + +#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 */ diff --git a/android/app/src/main/cpp/samplerate_stub.c b/android/app/src/main/cpp/samplerate_stub.c new file mode 100644 index 0000000000..2ca242bea1 --- /dev/null +++ b/android/app/src/main/cpp/samplerate_stub.c @@ -0,0 +1,209 @@ +#include +#include +#include + +#if defined(__aarch64__) +#include +#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 +} diff --git a/android/app/src/main/cpp/xemu_android.cpp b/android/app/src/main/cpp/xemu_android.cpp index 1b83c40865..74f97df53d 100644 --- a/android/app/src/main/cpp/xemu_android.cpp +++ b/android/app/src/main/cpp/xemu_android.cpp @@ -27,8 +27,40 @@ #include #include +#ifdef CONFIG_VULKAN +#include +#include +#include + +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( + 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(); +} diff --git a/android/app/src/main/cpp/xemu_settings_android.cc b/android/app/src/main/cpp/xemu_settings_android.cc index 906afec007..fdd41e0de9 100644 --- a/android/app/src/main/cpp/xemu_settings_android.cc +++ b/android/app/src/main/cpp/xemu_settings_android.cc @@ -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()) { + 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()) { - g_config.perf.hard_fpu = *hard_fpu; + if (auto fp_jit = perf["fp_jit"].value()) { + g_config.perf.fp_jit = *fp_jit; + } else if (auto hard_fpu = perf["hard_fpu"].value()) { + g_config.perf.fp_jit = *hard_fpu; } if (auto cache_shaders = perf["cache_shaders"].value()) { 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; +} diff --git a/android/app/src/main/java/com/izzy2lost/x1box/FrontendLaunchHelper.kt b/android/app/src/main/java/com/izzy2lost/x1box/FrontendLaunchHelper.kt index 54d9f1b1e4..f50dc8b7e1 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/FrontendLaunchHelper.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/FrontendLaunchHelper.kt @@ -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() diff --git a/android/app/src/main/java/com/izzy2lost/x1box/GpuDriverHelper.kt b/android/app/src/main/java/com/izzy2lost/x1box/GpuDriverHelper.kt new file mode 100644 index 0000000000..14a0f65b84 --- /dev/null +++ b/android/app/src/main/java/com/izzy2lost/x1box/GpuDriverHelper.kt @@ -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 { + 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 + ) +} diff --git a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt index 1adbd28b5e..08fd0fecfb 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/MainActivity.kt @@ -61,11 +61,33 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { private var startButtonDown = false private var selectButtonDown = false private var comboTriggered = false + private var suspendedByLifecycle = false + private var resumeEmulationOnMenuDismiss = false private var startupSnapshotSlot: Int? = null private var startupSnapshotLoadScheduled = false - @Volatile private var processTerminationScheduled = false private lateinit var swipeUpGestureRecognizer: SwipeUpGestureRecognizer + override fun loadLibraries() { + super.loadLibraries() + initializeGpuDriver() + } + + private fun initializeGpuDriver() { + GpuDriverHelper.init(this) + if (GpuDriverHelper.supportsCustomDriverLoading()) { + val driverLib = GpuDriverHelper.getInstalledDriverLibrary() + if (driverLib != null) { + android.util.Log.i(TAG, "GPU driver: loading custom driver=$driverLib") + GpuDriverHelper.initializeDriver(driverLib) + } else { + android.util.Log.i(TAG, "GPU driver: no custom driver installed, initializing system driver via adrenotools") + GpuDriverHelper.initializeDriver() + } + } else { + android.util.Log.i(TAG, "GPU driver: custom loading not supported on this device") + } + } + override fun createSDLSurface(context: Context): SDLSurface { return super.createSDLSurface(context).apply { layoutParams = RelativeLayout.LayoutParams( @@ -205,6 +227,10 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { override fun onResume() { super.onResume() + if (suspendedByLifecycle) { + nativeResumeEmulation() + suspendedByLifecycle = false + } OrientationLocker(this, landscapeOnly = true).enable() window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) mLayout?.keepScreenOn = true @@ -222,9 +248,20 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { swipeUpGestureRecognizer.reset() onScreenController?.resetAllInputs() controllerBridge?.reset() + resumeEmulationOnMenuDismiss = false + suspendedByLifecycle = true + nativePauseEmulation() super.onPause() } + private fun resumeEmulationIfSafe() { + val destroyed = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed + if (!suspendedByLifecycle && !isFinishing && !destroyed) { + nativeResumeEmulation() + } + } + private fun scheduleStartupSnapshotLoadIfRequested() { val slot = startupSnapshotSlot ?: return if (startupSnapshotLoadScheduled) { @@ -335,13 +372,9 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { override fun onDestroy() { DebugLog.i(TAG) { "onDestroy()" } swipeUpGestureRecognizer.reset() + resumeEmulationOnMenuDismiss = false inGameMenuDialog?.dismiss() inGameMenuDialog = null - val shouldTerminateProcess = isFinishing && !isChangingConfigurations - - if (shouldTerminateProcess) { - terminateXemuProcessSoon("activity finish") - } // Unregister virtual controller try { @@ -443,6 +476,9 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { private external fun nativeSaveSnapshot(name: String): Boolean private external fun nativeLoadSnapshot(name: String): Boolean private external fun nativeRebootSystem() + private external fun nativePauseEmulation() + private external fun nativeResumeEmulation() + private external fun nativeExitEmulation() private fun slotName(slot: Int) = "android_slot_$slot" @@ -637,6 +673,8 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { if (ok) getString(R.string.snapshot_loaded, slot) else getString(R.string.snapshot_load_failed, slot) } Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() + hideSystemUI() + resumeEmulationIfSafe() } }.start() } @@ -645,6 +683,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { val previews = loadSnapshotSlotPreviews() val listView = ListView(this) lateinit var dialog: AlertDialog + var operationStarted = false val adapter = object : BaseAdapter() { override fun getCount(): Int = previews.size @@ -684,6 +723,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { listView.adapter = adapter listView.setOnItemClickListener { _, _, position, _ -> val slot = previews[position].slot + operationStarted = true dialog.dismiss() runSnapshotOperation(slot, save) } @@ -695,6 +735,12 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { ) .setView(listView) .setNegativeButton(android.R.string.cancel, null) + .setOnDismissListener { + hideSystemUI() + if (!operationStarted) { + resumeEmulationIfSafe() + } + } .create() dialog.show() @@ -709,14 +755,22 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { } private fun showRebootSystemConfirmation() { + var confirmed = false MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog) .setTitle(R.string.in_game_menu_reboot_title) .setMessage(R.string.in_game_menu_reboot_message) .setPositiveButton(R.string.in_game_menu_reboot_action) { _, _ -> + confirmed = true onScreenController?.resetAllInputs() nativeRebootSystem() } .setNegativeButton(android.R.string.cancel, null) + .setOnDismissListener { + hideSystemUI() + if (!confirmed) { + resumeEmulationIfSafe() + } + } .show() } @@ -725,6 +779,8 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { if (inGameMenuDialog?.isShowing == true) { return } + nativePauseEmulation() + resumeEmulationOnMenuDismiss = true val dp = resources.displayMetrics.density val verticalButtonSpacing = (8 * dp).toInt() @@ -732,6 +788,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { lateinit var dialog: androidx.appcompat.app.AlertDialog data class MenuButtonSpec( val label: String, + val resumeAfterDismiss: Boolean = true, val action: () -> Unit, ) @@ -747,6 +804,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { isSingleLine = false maxLines = 2 setOnClickListener { + resumeEmulationOnMenuDismiss = spec.resumeAfterDismiss dialog.dismiss() spec.action() } @@ -804,19 +862,34 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { ) { toggleOnScreenController() } - val saveStateButton = MenuButtonSpec(getString(R.string.in_game_menu_save_state)) { + val saveStateButton = MenuButtonSpec( + getString(R.string.in_game_menu_save_state), + resumeAfterDismiss = false + ) { showSaveStateDialog() } - val loadStateButton = MenuButtonSpec(getString(R.string.in_game_menu_load_state)) { + val loadStateButton = MenuButtonSpec( + getString(R.string.in_game_menu_load_state), + resumeAfterDismiss = false + ) { showLoadStateDialog() } - val rebootButton = MenuButtonSpec(getString(R.string.in_game_menu_reboot_system)) { + val rebootButton = MenuButtonSpec( + getString(R.string.in_game_menu_reboot_system), + resumeAfterDismiss = false + ) { showRebootSystemConfirmation() } - val exitToLibraryButton = MenuButtonSpec(getString(R.string.in_game_menu_exit_to_library)) { + val exitToLibraryButton = MenuButtonSpec( + getString(R.string.in_game_menu_exit_to_library), + resumeAfterDismiss = false + ) { exitToGameLibrary() } - val quitAppButton = MenuButtonSpec(getString(R.string.in_game_menu_quit_app)) { + val quitAppButton = MenuButtonSpec( + getString(R.string.in_game_menu_quit_app), + resumeAfterDismiss = false + ) { quitApp() } @@ -848,6 +921,10 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { .setOnDismissListener { inGameMenuDialog = null hideSystemUI() + if (resumeEmulationOnMenuDismiss) { + resumeEmulationIfSafe() + } + resumeEmulationOnMenuDismiss = false } .create() @@ -865,36 +942,19 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener { } private fun exitToGameLibrary() { + nativeExitEmulation() val intent = Intent(this, GameLibraryActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) } startActivity(intent) - terminateXemuProcessSoon("exit to library") finish() } private fun quitApp() { - terminateXemuProcessSoon("quit app") + nativeExitEmulation() finishAffinity() } - private fun terminateXemuProcessSoon(reason: String) { - if (processTerminationScheduled) { - return - } - processTerminationScheduled = true - - Thread { - try { - Thread.sleep(350) - } catch (_: InterruptedException) { - Thread.currentThread().interrupt() - } - DebugLog.i(TAG) { "Terminating :xemu process after $reason" } - Process.killProcess(Process.myPid()) - }.start() - } - override fun getLibraries(): Array = arrayOf( "SDL2", "xemu", diff --git a/android/app/src/main/java/com/izzy2lost/x1box/PerGameSettingsActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/PerGameSettingsActivity.kt index 6ec698a3ff..105b57c42a 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/PerGameSettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/PerGameSettingsActivity.kt @@ -248,7 +248,7 @@ class PerGameSettingsActivity : AppCompatActivity() { private fun readGlobalValue(key: String): String { val prefs = getSharedPreferences("x1box_prefs", MODE_PRIVATE) return when (key) { - "setting_renderer" -> prefs.getString(key, "opengl") ?: "opengl" + "setting_renderer" -> prefs.getString(key, "vulkan") ?: "vulkan" "setting_filtering" -> prefs.getString(key, "linear") ?: "linear" "setting_vsync" -> prefs.getBoolean(key, false).toString() "setting_surface_scale" -> prefs.getInt(key, 1).toString() diff --git a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt index d3b86998e5..4088403d37 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt @@ -41,12 +41,8 @@ class SettingsActivity : AppCompatActivity() { private const val PREF_HRTF_DEFAULT_OFF_MIGRATED = "setting_hrtf_default_off_migrated_v1" private const val PREF_INSIGNIA_SETUP_URI = "setting_insignia_setup_assistant_uri" private const val PREF_INSIGNIA_SETUP_NAME = "setting_insignia_setup_assistant_name" - private const val PREF_VULKAN_DRIVER_URI = "setting_vulkan_driver_uri" - private const val PREF_VULKAN_DRIVER_NAME = "setting_vulkan_driver_name" - private const val PREF_VULKAN_DRIVER_PATH = "setting_vulkan_driver_path" private const val INSIGNIA_SIGN_UP_URL = "https://insignia.live/" private const val INSIGNIA_GUIDE_URL = "https://insignia.live/guide/connect" - private const val VULKAN_DRIVER_FILE_NAME = "vulkan_driver.so" private const val MANAGED_FILES_ARCHIVE_PREFIX = "x1box-files-" private val MANAGED_EMULATOR_FILE_ORDER = listOf( "mcpx.bin", @@ -95,10 +91,6 @@ class SettingsActivity : AppCompatActivity() { val hadFailures: Boolean, ) - private data class VulkanDriverImportResult( - val file: File, - val displayName: String, - ) private data class DashboardImportPlan( val hddFile: File, @@ -171,9 +163,6 @@ class SettingsActivity : AppCompatActivity() { GameOrientationOption(OrientationPreferences.GameOrientation.REVERSE_LANDSCAPE, R.string.settings_orientation_reverse_landscape), ) - private var pendingVulkanUri: String? = null - private var pendingVulkanName: String? = null - private var clearVulkan = false private var isInitializingHdd = false private var isImportingDashboard = false private var isImportingEmulatorFiles = false @@ -184,7 +173,11 @@ class SettingsActivity : AppCompatActivity() { private lateinit var btnExportEmulatorFiles: MaterialButton private lateinit var switchDebugLogs: MaterialSwitch private lateinit var switchNetworkEnable: MaterialSwitch - private lateinit var tvVulkanDriverName: TextView + private lateinit var driverStatusText: TextView + private lateinit var gpuNotSupportedText: TextView + private lateinit var btnInstallDriver: MaterialButton + private lateinit var btnSelectDriver: MaterialButton + private lateinit var btnResetDriver: MaterialButton private lateinit var tvInsigniaStatus: TextView private lateinit var tvEepromStatus: TextView private lateinit var tvHddToolsStatus: TextView @@ -219,19 +212,9 @@ class SettingsActivity : AppCompatActivity() { private var eepromMissing = false private var eepromError = false - private val pickDriver = + private val pickDriverZip = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? -> - uri ?: return@registerForActivityResult - val selectedName = getFileName(uri) ?: uri.lastPathSegment ?: "custom_driver.so" - if (!isSupportedVulkanDriverSelection(selectedName)) { - Toast.makeText(this, R.string.settings_vulkan_driver_pick_error, Toast.LENGTH_LONG).show() - return@registerForActivityResult - } - persistUriPermission(uri) - pendingVulkanUri = uri.toString() - pendingVulkanName = selectedName - clearVulkan = false - tvVulkanDriverName.text = pendingVulkanName + if (uri != null) installDriverFromUri(uri) } private val pickDashboardZip = @@ -348,9 +331,11 @@ class SettingsActivity : AppCompatActivity() { layoutAdvancedExperimentalContent = findViewById(R.id.layout_advanced_experimental_content) dropdownUiOrientation = findViewById(R.id.dropdown_app_orientation) dropdownGameOrientation = findViewById(R.id.dropdown_game_orientation) - tvVulkanDriverName = findViewById(R.id.tv_vulkan_driver_name) - val btnVulkanBrowse = findViewById(R.id.btn_vulkan_browse) - val btnVulkanClear = findViewById(R.id.btn_vulkan_clear) + driverStatusText = findViewById(R.id.settings_gpu_driver_status) + gpuNotSupportedText = findViewById(R.id.settings_gpu_not_supported) + btnInstallDriver = findViewById(R.id.btn_install_driver) + btnSelectDriver = findViewById(R.id.btn_select_driver) + btnResetDriver = findViewById(R.id.btn_reset_driver) tvEepromStatus = findViewById(R.id.tv_eeprom_status) tvHddToolsStatus = findViewById(R.id.tv_hdd_tools_status) inputEepromLanguage = findViewById(R.id.input_eeprom_language) @@ -368,7 +353,7 @@ class SettingsActivity : AppCompatActivity() { updateEmulatorFilesActionState() // Load current values - val renderer = prefs.getString("setting_renderer", "opengl") ?: "opengl" + val renderer = prefs.getString("setting_renderer", "vulkan") ?: "vulkan" if (renderer == "opengl") { toggleGraphicsApi.check(R.id.btn_renderer_opengl) } else { @@ -410,10 +395,21 @@ class SettingsActivity : AppCompatActivity() { else -> toggleSystemMemory.check(R.id.btn_memory_64) } - tvVulkanDriverName.text = - prefs.getString(PREF_VULKAN_DRIVER_NAME, null) - ?: prefs.getString(PREF_VULKAN_DRIVER_PATH, null)?.let { File(it).name } - ?: getString(R.string.settings_vulkan_driver_none) + GpuDriverHelper.init(this) + val supportsCustomDriver = GpuDriverHelper.supportsCustomDriverLoading() + if (!supportsCustomDriver) { + gpuNotSupportedText.visibility = View.VISIBLE + btnInstallDriver.isEnabled = false + btnSelectDriver.isEnabled = false + btnResetDriver.isEnabled = false + } + refreshDriverStatus() + + btnInstallDriver.setOnClickListener { + pickDriverZip.launch(arrayOf("application/zip", "application/octet-stream")) + } + btnSelectDriver.setOnClickListener { showDriverSelectionDialog() } + btnResetDriver.setOnClickListener { confirmResetDriver() } val tcgThread = prefs.getString("setting_tcg_thread", "multi") ?: "multi" if (tcgThread == "single") { @@ -441,17 +437,6 @@ class SettingsActivity : AppCompatActivity() { else -> toggleAudioDriver.check(R.id.btn_audio_opensles) } - btnVulkanBrowse.setOnClickListener { - pickDriver.launch(arrayOf("*/*")) - } - - btnVulkanClear.setOnClickListener { - pendingVulkanUri = null - pendingVulkanName = null - clearVulkan = true - tvVulkanDriverName.text = getString(R.string.settings_vulkan_driver_none) - } - btnRedoSetup.setOnClickListener { prefs.edit().putBoolean("setup_complete", false).apply() startActivity(Intent(this, SetupWizardActivity::class.java)) @@ -484,19 +469,6 @@ class SettingsActivity : AppCompatActivity() { } fun persistSettings(): Pair { - val pendingVulkanUriValue = pendingVulkanUri - val importedVulkanDriver = when { - clearVulkan -> { - deleteImportedVulkanDriver() - null - } - pendingVulkanUriValue != null -> importCustomVulkanDriver( - uri = Uri.parse(pendingVulkanUriValue), - selectedName = pendingVulkanName, - ) - else -> null - } - val selectedDisplayMode = when (toggleDisplayMode.checkedButtonId) { R.id.btn_display_4_3 -> 1 R.id.btn_display_16_9 -> 2 @@ -551,17 +523,6 @@ class SettingsActivity : AppCompatActivity() { .putString("setting_filtering", selectedFiltering) .putString("setting_renderer", selectedRenderer) - when { - clearVulkan -> edit - .remove(PREF_VULKAN_DRIVER_URI) - .remove(PREF_VULKAN_DRIVER_NAME) - .remove(PREF_VULKAN_DRIVER_PATH) - importedVulkanDriver != null -> edit - .remove(PREF_VULKAN_DRIVER_URI) - .putString(PREF_VULKAN_DRIVER_NAME, importedVulkanDriver.displayName) - .putString(PREF_VULKAN_DRIVER_PATH, importedVulkanDriver.file.absolutePath) - } - edit.apply() DebugLog.setEnabled( context = this@SettingsActivity, @@ -619,10 +580,7 @@ class SettingsActivity : AppCompatActivity() { } catch (error: Exception) { Toast.makeText( this, - getString( - R.string.settings_vulkan_driver_import_failed, - error.message ?: error.javaClass.simpleName, - ), + "Failed to save settings: ${error.message ?: error.javaClass.simpleName}", Toast.LENGTH_LONG ).show() } @@ -640,174 +598,72 @@ class SettingsActivity : AppCompatActivity() { .apply() } - private fun importCustomVulkanDriver( - uri: Uri, - selectedName: String?, - ): VulkanDriverImportResult { - val targetFile = resolveCustomVulkanDriverFile() - val parent = targetFile.parentFile - ?: throw IOException("Failed to prepare the custom Vulkan driver folder.") - if (!parent.exists() && !parent.mkdirs()) { - throw IOException("Failed to prepare the custom Vulkan driver folder.") - } - - val tempFile = File(parent, "${targetFile.name}.tmp") - if (tempFile.exists() && !tempFile.delete()) { - throw IOException("Failed to replace the staged custom Vulkan driver.") - } - - try { - if (isZipSelection(uri)) { - extractVulkanDriverZipToFile(uri, tempFile) - } else { - copyUriToFile(uri, tempFile) - } - moveFileIntoPlace(tempFile, targetFile) - } catch (error: Exception) { - tempFile.delete() - throw error - } - - return VulkanDriverImportResult( - file = targetFile, - displayName = selectedName?.takeIf { it.isNotBlank() } ?: targetFile.name, - ) - } - - private fun copyUriToFile( - uri: Uri, - target: File, - openError: String = "Failed to open the selected file.", - ) { - val parent = target.parentFile - if (parent != null && !parent.exists() && !parent.mkdirs()) { - throw IOException("Failed to prepare ${parent.absolutePath}.") - } - contentResolver.openInputStream(uri)?.use { input -> - FileOutputStream(target).use { output -> - input.copyTo(output) - } - } ?: throw IOException(openError) - } - - private fun extractVulkanDriverZipToFile(uri: Uri, target: File) { - val selectedEntryName = findVulkanDriverZipEntry(uri) - - contentResolver.openInputStream(uri)?.use { rawInput -> - ZipInputStream(BufferedInputStream(rawInput)).use { zip -> - while (true) { - val entry = zip.nextEntry ?: break - if (entry.isDirectory) { - zip.closeEntry() - continue - } - if (entry.name == selectedEntryName) { - FileOutputStream(target).use { output -> - zip.copyTo(output) - } - zip.closeEntry() - return - } - zip.closeEntry() + private fun installDriverFromUri(uri: Uri) { + Thread { + val success = GpuDriverHelper.installDriverFromUri(this, uri) + runOnUiThread { + if (success) { + Toast.makeText(this, getString(R.string.settings_gpu_driver_installed), Toast.LENGTH_SHORT).show() + refreshDriverStatus() + } else { + Toast.makeText(this, getString(R.string.settings_gpu_driver_install_failed), Toast.LENGTH_SHORT).show() } } - } ?: throw IOException("Failed to open the selected Vulkan driver ZIP.") - - throw IOException("The selected ZIP did not contain a readable Vulkan driver library.") + }.start() } - private fun findVulkanDriverZipEntry(uri: Uri): String { - var bestEntry: String? = null - var bestScore = Int.MIN_VALUE - - contentResolver.openInputStream(uri)?.use { rawInput -> - ZipInputStream(BufferedInputStream(rawInput)).use { zip -> - while (true) { - val entry = zip.nextEntry ?: break - if (!entry.isDirectory) { - val normalizedName = entry.name.replace('\\', '/') - if (isSharedLibrarySelection(normalizedName)) { - val score = scoreVulkanDriverZipEntry(normalizedName) - if (score > bestScore) { - bestScore = score - bestEntry = entry.name - } - } + private fun showDriverSelectionDialog() { + val drivers = GpuDriverHelper.getAvailableDrivers() + if (drivers.isEmpty()) { + Toast.makeText(this, getString(R.string.settings_gpu_driver_none_available), Toast.LENGTH_SHORT).show() + return + } + val labels = drivers.map { driver -> + buildString { + append(driver.name ?: "Unknown") + if (!driver.description.isNullOrBlank()) { append("\n"); append(driver.description) } + if (!driver.author.isNullOrBlank()) { append("\nby "); append(driver.author) } + } + }.toTypedArray() + MaterialAlertDialogBuilder(this) + .setTitle(R.string.settings_gpu_driver_select_title) + .setItems(labels) { _, which -> + val selected = drivers[which] + if (selected.path != null) { + val zipFile = File(selected.path) + val success = GpuDriverHelper.installDriver(zipFile) + if (success) { + Toast.makeText(this, getString(R.string.settings_gpu_driver_installed), Toast.LENGTH_SHORT).show() + refreshDriverStatus() + } else { + Toast.makeText(this, getString(R.string.settings_gpu_driver_install_failed), Toast.LENGTH_SHORT).show() } - zip.closeEntry() } } - } ?: throw IOException("Failed to open the selected Vulkan driver ZIP.") - - return bestEntry - ?: throw IOException("The selected ZIP did not contain any .so files.") + .setNegativeButton(android.R.string.cancel, null) + .show() } - private fun scoreVulkanDriverZipEntry(entryName: String): Int { - val normalizedPath = entryName.replace('\\', '/').lowercase(Locale.US) - val fileName = normalizedPath.substringAfterLast('/') - val depth = normalizedPath.count { it == '/' } - var score = 0 - - score += when (fileName) { - "vulkan.adreno.so" -> 4_000 - "libvulkan_freedreno.so" -> 3_800 - else -> 0 - } - if (fileName.startsWith("libvulkan")) { - score += 3_000 - } - if (fileName.startsWith("vulkan")) { - score += 2_700 - } - if (fileName.contains("vulkan")) { - score += 2_000 - } - if (fileName.contains("adreno")) { - score += 900 - } - if (fileName.contains("turnip") || fileName.contains("freedreno") || fileName.contains("mesa")) { - score += 700 - } - if (normalizedPath.contains("/arm64-v8a/")) { - score += 450 - } - if (normalizedPath.contains("/lib/")) { - score += 120 - } - score += 320 - (depth * 40) - - return score - } - - private fun moveFileIntoPlace(source: File, target: File) { - if (target.exists() && !target.delete()) { - throw IOException("Failed to replace the staged custom Vulkan driver.") - } - if (!source.renameTo(target)) { - source.copyTo(target, overwrite = true) - if (!source.delete()) { - source.deleteOnExit() + private fun confirmResetDriver() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.settings_gpu_driver_reset_title) + .setMessage(R.string.settings_gpu_driver_reset_message) + .setPositiveButton(R.string.settings_gpu_driver_reset) { _, _ -> + GpuDriverHelper.installDefaultDriver() + Toast.makeText(this, getString(R.string.settings_gpu_driver_reset_done), Toast.LENGTH_SHORT).show() + refreshDriverStatus() } - } + .setNegativeButton(android.R.string.cancel, null) + .show() } - private fun deleteImportedVulkanDriver() { - val targetFile = resolveCustomVulkanDriverFile() - if (targetFile.exists() && !targetFile.delete()) { - targetFile.deleteOnExit() + private fun refreshDriverStatus() { + val name = GpuDriverHelper.getInstalledDriverName() + driverStatusText.text = if (name != null) { + getString(R.string.settings_gpu_driver_active, name) + } else { + getString(R.string.settings_gpu_driver_system) } - - val parent = targetFile.parentFile ?: return - val tempFile = File(parent, "${targetFile.name}.tmp") - if (tempFile.exists() && !tempFile.delete()) { - tempFile.deleteOnExit() - } - } - - private fun resolveCustomVulkanDriverFile(): File { - val base = getExternalFilesDir(null) ?: filesDir - return File(File(base, "x1box"), VULKAN_DRIVER_FILE_NAME) } private fun updateEmulatorFilesActionState() { @@ -1195,7 +1051,8 @@ class SettingsActivity : AppCompatActivity() { ?.let { editor.putBoolean(PREF_HRTF, it) } parseTomlBoolean(sections, "perf", "cache_shaders") ?.let { editor.putBoolean("setting_cache_shaders", it) } - parseTomlBoolean(sections, "perf", "hard_fpu") + (parseTomlBoolean(sections, "perf", "fp_jit") + ?: parseTomlBoolean(sections, "perf", "hard_fpu")) ?.let { editor.putBoolean("setting_hard_fpu", it) } parseTomlString(sections, "android", "tcg_thread") ?.lowercase(Locale.US) @@ -2622,19 +2479,23 @@ class SettingsActivity : AppCompatActivity() { private fun isZipSelection(uri: Uri): Boolean { val name = getFileName(uri) ?: uri.lastPathSegment ?: return false - return isZipSelection(name) - } - - private fun isZipSelection(name: String): Boolean { return name.lowercase(Locale.US).endsWith(".zip") } - private fun isSharedLibrarySelection(name: String): Boolean { - return name.lowercase(Locale.US).endsWith(".so") - } - - private fun isSupportedVulkanDriverSelection(name: String): Boolean { - return isSharedLibrarySelection(name) || isZipSelection(name) + private fun copyUriToFile( + uri: Uri, + target: File, + openError: String = "Failed to open the selected file.", + ) { + val parent = target.parentFile + if (parent != null && !parent.exists() && !parent.mkdirs()) { + throw IOException("Failed to prepare ${parent.absolutePath}.") + } + contentResolver.openInputStream(uri)?.use { input -> + FileOutputStream(target).use { output -> + input.copyTo(output) + } + } ?: throw IOException(openError) } private fun persistUriPermission(uri: Uri) { @@ -2763,6 +2624,7 @@ class SettingsActivity : AppCompatActivity() { private fun isPersistentCacheEntry(name: String): Boolean { return name == "shaders" || + name == "tb_cache.bin" || name == "shader_cache_list" || name.startsWith("scache-") || name.startsWith("vk_pipeline_cache_") diff --git a/android/app/src/main/res/layout/activity_settings.xml b/android/app/src/main/res/layout/activity_settings.xml index b72f1c306b..e9d1fa2b72 100644 --- a/android/app/src/main/res/layout/activity_settings.xml +++ b/android/app/src/main/res/layout/activity_settings.xml @@ -313,53 +313,72 @@ android:layout_height="wrap_content" android:layout_marginBottom="12dp" android:background="@drawable/section_header_background" - android:text="@string/settings_section_vulkan_driver" + android:text="@string/settings_gpu_driver_title" android:textAppearance="@style/TextAppearance.Material3.LabelLarge" android:textColor="@color/xemu_green_light" /> + + + + + + + android:text="@string/settings_gpu_driver_select" /> + android:text="@string/settings_gpu_driver_reset" /> - - VSync Finish core setup before launching from a frontend. Could not access that game from the frontend intent. Pick the same folder in x1box first, or use a content URI. - Custom Vulkan Driver - System Default - Browse… - Clear - For Adreno GPUs with Turnip/Mesa. Pick a driver .so or .zip package. Restart required. - Pick a Vulkan driver .so or .zip file. - Failed to import the custom Vulkan driver: %1$s + GPU Driver + Use a custom Vulkan driver for better performance or compatibility on Adreno devices. + Active driver: System default + Active driver: %1$s + Custom GPU driver loading is not supported on this device. + Install Driver from ZIP + Select Driver + Select a GPU Driver + Use System Driver + Reset GPU Driver + Revert to the system default GPU driver? The change takes effect on next launch. + GPU driver reset to system default. Restart the app to apply. + Driver installed successfully. Restart the app to apply. + Failed to install driver. The ZIP may be invalid or incompatible. + No driver ZIPs found. Use \"Install Driver from ZIP\" to add one first. Aspect Ratio Stretch 4:3 diff --git a/config_spec.yml b/config_spec.yml index 3e36d1e5c6..f80988989d 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -364,7 +364,7 @@ sys: dvd_path: string perf: - hard_fpu: + fp_jit: type: bool default: true cache_shaders: diff --git a/hw/xbox/nv2a/pgraph/gl/constants.h b/hw/xbox/nv2a/pgraph/gl/constants.h index b6fa07e01f..ac9e4e781e 100644 --- a/hw/xbox/nv2a/pgraph/gl/constants.h +++ b/hw/xbox/nv2a/pgraph/gl/constants.h @@ -32,51 +32,12 @@ #define NV2A_GL_Z16_INTERNAL GL_DEPTH_COMPONENT16 #define NV2A_GL_Z16_TYPE GL_UNSIGNED_SHORT #define NV2A_GL_Z16_FLOAT_TYPE GL_UNSIGNED_SHORT -/* GLES requires GL_DEPTH24_STENCIL8 when format=GL_DEPTH_STENCIL */ -#define NV2A_GL_DEPTH_X8_Y24_INTERNAL GL_DEPTH24_STENCIL8 -# ifdef GL_TEXTURE_LOD_BIAS -# define NV2A_GL_TEXTURE_LOD_BIAS GL_TEXTURE_LOD_BIAS -# elif defined(GL_TEXTURE_LOD_BIAS_EXT) -# define NV2A_GL_TEXTURE_LOD_BIAS GL_TEXTURE_LOD_BIAS_EXT -# else -# define NV2A_GL_TEXTURE_LOD_BIAS 0 -# endif -# ifdef GL_ANY_SAMPLES_PASSED -# define NV2A_GL_ZPASS_QUERY_TARGET GL_ANY_SAMPLES_PASSED -# elif defined(GL_ANY_SAMPLES_PASSED_EXT) -# define NV2A_GL_ZPASS_QUERY_TARGET GL_ANY_SAMPLES_PASSED_EXT -# else -# define NV2A_GL_ZPASS_QUERY_TARGET 0 -# endif -# ifdef GL_CLAMP_TO_BORDER -# define NV2A_GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER -# elif defined(GL_CLAMP_TO_BORDER_EXT) -# define NV2A_GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_EXT -# elif defined(GL_CLAMP_TO_BORDER_OES) -# define NV2A_GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_OES -# else -# define NV2A_GL_CLAMP_TO_BORDER 0 -# endif -# ifdef GL_TEXTURE_BORDER_COLOR -# define NV2A_GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR -# elif defined(GL_TEXTURE_BORDER_COLOR_EXT) -# define NV2A_GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_EXT -# elif defined(GL_TEXTURE_BORDER_COLOR_OES) -# define NV2A_GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_OES -# else -# define NV2A_GL_TEXTURE_BORDER_COLOR 0 -# endif #else #define NV2A_GL_UNSIGNED_INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8 #define NV2A_GL_UNSIGNED_INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV #define NV2A_GL_Z16_INTERNAL GL_DEPTH_COMPONENT32F #define NV2A_GL_Z16_TYPE GL_HALF_FLOAT #define NV2A_GL_Z16_FLOAT_TYPE GL_HALF_FLOAT -#define NV2A_GL_DEPTH_X8_Y24_INTERNAL GL_DEPTH_COMPONENT -#define NV2A_GL_TEXTURE_LOD_BIAS GL_TEXTURE_LOD_BIAS -#define NV2A_GL_ZPASS_QUERY_TARGET GL_SAMPLES_PASSED -#define NV2A_GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER -#define NV2A_GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR #endif static const GLenum pgraph_texture_min_filter_gl_map[] = { @@ -103,7 +64,7 @@ static const GLenum pgraph_texture_addr_gl_map[] = { GL_REPEAT, GL_MIRRORED_REPEAT, GL_CLAMP_TO_EDGE, - NV2A_GL_CLAMP_TO_BORDER, + GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE, /* Approximate GL_CLAMP */ }; @@ -297,11 +258,11 @@ static const ColorFormatInfo kelvin_color_format_gl_map[66] = { {2, false, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, {GL_RED, GL_ZERO, GL_ZERO, GL_ZERO}, true}, [NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_X8_Y24_FIXED] = - {4, true, NV2A_GL_DEPTH_X8_Y24_INTERNAL, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + {4, true, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, {GL_RED, GL_ONE, GL_ZERO, GL_ZERO}, true}, [NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_X8_Y24_FLOAT] = /* FIXME: Uses fixed-point format to match surface format hack below. */ - {4, true, NV2A_GL_DEPTH_X8_Y24_INTERNAL, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + {4, true, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, {GL_RED, GL_ONE, GL_ZERO, GL_ZERO}, true}, [NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_Y16_FIXED] = {2, true, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, @@ -373,3 +334,5 @@ static const SurfaceFormatInfo kelvin_surface_zeta_fixed_format_gl_map[] = { }; #endif + + diff --git a/hw/xbox/nv2a/pgraph/gl/display.c b/hw/xbox/nv2a/pgraph/gl/display.c index abdca33b02..d0a7b56e40 100644 --- a/hw/xbox/nv2a/pgraph/gl/display.c +++ b/hw/xbox/nv2a/pgraph/gl/display.c @@ -28,26 +28,13 @@ #include #ifdef __ANDROID__ #include -#include -/* EGL_EGLEXT_PROTOTYPES enables KHR function declarations in eglext.h. - * We use KHR variants because eglCreateSync (EGL 1.5) is only in - * libEGL.so stubs from API 29+; the KHR equivalents exist from API 21. */ -#define EGL_EGLEXT_PROTOTYPES -#include -#undef EGL_EGLEXT_PROTOTYPES #endif #ifdef __ANDROID__ -extern bool xemu_android_is_debug_logging_enabled(void); - static void gl_log_errors(const char *ctx) { GLenum gl_err; - if (!xemu_android_is_debug_logging_enabled()) { - return; - } - while ((gl_err = glGetError()) != GL_NO_ERROR) { __android_log_print(ANDROID_LOG_WARN, "xemu-android", "GL error 0x%X at %s", gl_err, ctx); @@ -515,6 +502,7 @@ static void render_display(NV2AState *d, SurfaceBinding *surface) return; } + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); glBindVertexArray(r->disp_rndr.vao); glBindBuffer(GL_ARRAY_BUFFER, r->disp_rndr.vbo); @@ -546,23 +534,8 @@ static void render_display(NV2AState *d, SurfaceBinding *surface) static void gl_fence(void) { #ifdef __ANDROID__ - /* GL sync objects fail with GL_INVALID_OPERATION on Adreno when shared - * across EGL contexts. EGL_KHR_fence_sync operates at the EGL level and - * is not context-specific, so it is immune to this bug. - * We use the KHR variants because they are present in libEGL.so from - * API 21+; the EGL 1.5 core equivalents only appear from API 29. */ - EGLDisplay dpy = eglGetCurrentDisplay(); - if (dpy != EGL_NO_DISPLAY) { - EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, NULL); - if (sync != EGL_NO_SYNC_KHR) { - eglClientWaitSyncKHR(dpy, sync, - EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, - EGL_FOREVER_KHR); - eglDestroySyncKHR(dpy, sync); - return; - } - } - /* Fallback: EGL sync unavailable */ + /* Shared-context sync objects are still producing invalid-operation + * failures on Adreno. Favor correctness over throughput here. */ glFinish(); #else GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c index 474f55af1a..c802ddaa3c 100644 --- a/hw/xbox/nv2a/pgraph/gl/draw.c +++ b/hw/xbox/nv2a/pgraph/gl/draw.c @@ -27,21 +27,6 @@ #ifdef __ANDROID__ #include - -static void android_log_gl_errors(const char *ctx) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at %s", err, ctx); - } -} -#else -static inline void android_log_gl_errors(const char *ctx) -{ - (void)ctx; -} #endif void pgraph_gl_clear_surface(NV2AState *d, uint32_t parameter) @@ -350,12 +335,10 @@ void pgraph_gl_draw_begin(NV2AState *d) glScissor(xmin, ymin, scissor_width, scissor_height); /* Visibility testing */ - bool zpass_query_enabled = pg->zpass_pixel_count_enable; -#ifdef __ANDROID__ - zpass_query_enabled = zpass_query_enabled && - r->supported_extensions.occlusion_query_boolean; -#endif - if (zpass_query_enabled) { + /* GL_SAMPLES_PASSED is desktop-only; GLES has GL_ANY_SAMPLES_PASSED but + * only returns boolean. Skip occlusion queries entirely on Android. */ +#ifndef __ANDROID__ + if (pg->zpass_pixel_count_enable) { r->gl_zpass_pixel_count_query_count++; r->gl_zpass_pixel_count_queries = (GLuint*)g_realloc( r->gl_zpass_pixel_count_queries, @@ -365,10 +348,9 @@ void pgraph_gl_draw_begin(NV2AState *d) glGenQueries(1, &gl_query); r->gl_zpass_pixel_count_queries[ r->gl_zpass_pixel_count_query_count - 1] = gl_query; - glBeginQuery(NV2A_GL_ZPASS_QUERY_TARGET, gl_query); + glBeginQuery(GL_SAMPLES_PASSED, gl_query); } - - android_log_gl_errors("pgraph_gl_draw_begin"); +#endif } void pgraph_gl_draw_end(NV2AState *d) @@ -402,15 +384,12 @@ void pgraph_gl_draw_end(NV2AState *d) pgraph_gl_flush_draw(d); /* End of visibility testing */ - bool zpass_query_enabled = pg->zpass_pixel_count_enable; -#ifdef __ANDROID__ - zpass_query_enabled = zpass_query_enabled && - r->supported_extensions.occlusion_query_boolean; -#endif - if (zpass_query_enabled) { +#ifndef __ANDROID__ + if (pg->zpass_pixel_count_enable) { nv2a_profile_inc_counter(NV2A_PROF_QUERY); - glEndQuery(NV2A_GL_ZPASS_QUERY_TARGET); + glEndQuery(GL_SAMPLES_PASSED); } +#endif pg->draw_time++; #ifdef __ANDROID__ @@ -477,7 +456,6 @@ void pgraph_gl_flush_draw(NV2AState *d) pg->draw_arrays_start, pg->draw_arrays_count, pg->draw_arrays_length); } - android_log_gl_errors("pgraph_gl_flush_draw: draw_arrays"); } else if (pg->inline_elements_length) { NV2A_GL_DPRINTF(false, "Inline Elements"); nv2a_profile_inc_counter(NV2A_PROF_INLINE_ELEMENTS); @@ -539,7 +517,6 @@ void pgraph_gl_flush_draw(NV2AState *d) pg->inline_elements_length, GL_UNSIGNED_INT, (void *)0); } - android_log_gl_errors("pgraph_gl_flush_draw: inline_elements"); } else if (pg->inline_buffer_length) { NV2A_GL_DPRINTF(false, "Inline Buffer"); nv2a_profile_inc_counter(NV2A_PROF_INLINE_BUFFERS); @@ -586,7 +563,6 @@ void pgraph_gl_flush_draw(NV2AState *d) glDrawArrays(r->shader_binding->gl_primitive_mode, 0, pg->inline_buffer_length); } - android_log_gl_errors("pgraph_gl_flush_draw: inline_buffer"); } else if (pg->inline_array_length) { NV2A_GL_DPRINTF(false, "Inline Array"); nv2a_profile_inc_counter(NV2A_PROF_INLINE_ARRAYS); @@ -608,7 +584,6 @@ void pgraph_gl_flush_draw(NV2AState *d) glDrawArrays(r->shader_binding->gl_primitive_mode, 0, index_count); } - android_log_gl_errors("pgraph_gl_flush_draw: inline_array"); } else { NV2A_GL_DPRINTF(true, "EMPTY NV097_SET_BEGIN_END"); NV2A_UNCONFIRMED("EMPTY NV097_SET_BEGIN_END"); diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.c b/hw/xbox/nv2a/pgraph/gl/renderer.c index 76ce584e14..cdc094a177 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.c +++ b/hw/xbox/nv2a/pgraph/gl/renderer.c @@ -47,11 +47,6 @@ static bool gl_extension_list_has(const char *exts, const char *ext) } return false; } - -static bool gl_extension_supported(const char *exts, const char *ext) -{ - return gl_extension_list_has(exts, ext) || glo_check_extension(ext); -} #endif static void early_context_init(void) @@ -107,36 +102,12 @@ static void pgraph_gl_init(NV2AState *d, Error **errp) #ifdef __ANDROID__ const char *exts = (const char *)glGetString(GL_EXTENSIONS); - const char *glsl_version = (const char *)glGetString( - GL_SHADING_LANGUAGE_VERSION); - int glsl_es_major = 0; - int glsl_es_minor = 0; - r->bgra_supported = gl_extension_supported(exts, "GL_EXT_texture_format_BGRA8888") || - gl_extension_supported(exts, "GL_OES_texture_format_BGRA8888") || - gl_extension_supported(exts, "GL_EXT_texture_format_BGRA8888_OES"); - r->gles_version = 300; - if (glsl_version != NULL) { - while (*glsl_version != '\0' && !g_ascii_isdigit(*glsl_version)) { - glsl_version++; - } - if (sscanf(glsl_version, "%d.%d", &glsl_es_major, &glsl_es_minor) == - 2) { - if (glsl_es_minor < 10) { - glsl_es_minor *= 10; - } - r->gles_version = glsl_es_major * 100 + glsl_es_minor; - r->gles_version = MIN(r->gles_version, 320); - } - } - r->geometry_shaders_supported = - r->gles_version >= 320 || - gl_extension_supported(exts, "GL_EXT_geometry_shader") || - gl_extension_supported(exts, "GL_OES_geometry_shader"); + r->bgra_supported = gl_extension_list_has(exts, "GL_EXT_texture_format_BGRA8888") || + gl_extension_list_has(exts, "GL_OES_texture_format_BGRA8888") || + gl_extension_list_has(exts, "GL_EXT_texture_format_BGRA8888_OES"); __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "pgraph_gl_init: bgra_supported=%s geom_shader=%s glsl_es=%d", - r->bgra_supported ? "yes" : "no", - r->geometry_shaders_supported ? "yes" : "no", - r->gles_version); + "pgraph_gl_init: bgra_supported=%s", + r->bgra_supported ? "yes" : "no"); #endif #if DEBUG_NV2A_GL @@ -177,30 +148,7 @@ static void pgraph_gl_init(NV2AState *d, Error **errp) r->supported_extensions.texture_filter_anisotropic = glo_check_extension("GL_EXT_texture_filter_anisotropic"); - r->supported_extensions.texture_border_clamp = true; - r->supported_extensions.texture_lod_bias = true; - r->supported_extensions.occlusion_query_boolean = false; r->supported_extensions.max_texture_max_anisotropy = 1.0f; -#ifdef __ANDROID__ - r->supported_extensions.texture_border_clamp = - NV2A_GL_CLAMP_TO_BORDER != 0 && - NV2A_GL_TEXTURE_BORDER_COLOR != 0 && - (gl_extension_supported(exts, "GL_EXT_texture_border_clamp") || - gl_extension_supported(exts, "GL_OES_texture_border_clamp") || - gl_extension_supported(exts, "GL_NV_texture_border_clamp")); - r->supported_extensions.texture_lod_bias = - NV2A_GL_TEXTURE_LOD_BIAS != 0 && - gl_extension_supported(exts, "GL_EXT_texture_lod_bias"); - r->supported_extensions.occlusion_query_boolean = - NV2A_GL_ZPASS_QUERY_TARGET != 0 && - (epoxy_gl_version() >= 30 || - gl_extension_supported(exts, "GL_EXT_occlusion_query_boolean")); - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "pgraph_gl_init: texture_border_clamp=%s texture_lod_bias=%s occlusion_query_boolean=%s", - r->supported_extensions.texture_border_clamp ? "yes" : "no", - r->supported_extensions.texture_lod_bias ? "yes" : "no", - r->supported_extensions.occlusion_query_boolean ? "yes" : "no"); -#endif if (r->supported_extensions.texture_filter_anisotropic) { glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &r->supported_extensions.max_texture_max_anisotropy); @@ -232,7 +180,7 @@ static void pgraph_gl_finalize(NV2AState *d) static void pgraph_gl_flip_stall(NV2AState *d) { NV2A_GL_DFRAME_TERMINATOR(); - glFlush(); + glFinish(); } static void pgraph_gl_flush(NV2AState *d) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index c6428d041d..a800181365 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -193,20 +193,6 @@ typedef struct PGRAPHGLState { bool download_dirty_surfaces_pending; QemuEvent dirty_surfaces_download_complete; // common -#ifdef __ANDROID__ - GLuint gl_download_pbo; - size_t gl_download_pbo_size; - - /* Persistent scratch buffers for RGBA8 format conversion. - * Avoids per-frame g_malloc/g_free in surface upload/download hot paths. */ - uint8_t *android_conv_buf; /* upload conversion (pgraph_gl_upload_surface_data) */ - size_t android_conv_buf_size; - uint8_t *android_s2t_conv_buf; /* render-to-texture conversion */ - size_t android_s2t_conv_buf_size; - uint8_t *android_tex_conv_buf; /* texture upload conversion */ - size_t android_tex_conv_buf_size; -#endif - TextureBinding *texture_binding[NV2A_MAX_TEXTURES]; Lru texture_cache; TextureLruNode *texture_cache_entries; @@ -231,11 +217,6 @@ typedef struct PGRAPHGLState { struct s2t_rndr { GLuint fbo, vao, vbo, prog; GLuint tex_loc, surface_size_loc; -#ifdef __ANDROID__ - GLuint depth_prog; - GLuint depth_tex_loc; - GLint depth_scale_loc; -#endif } s2t_rndr; struct disp_rndr { @@ -261,16 +242,11 @@ typedef struct PGRAPHGLState { struct supported_extensions { GLboolean texture_filter_anisotropic; - GLboolean texture_border_clamp; - GLboolean texture_lod_bias; - GLboolean occlusion_query_boolean; GLfloat max_texture_max_anisotropy; } supported_extensions; #ifdef __ANDROID__ bool bgra_supported; - bool geometry_shaders_supported; - int gles_version; #endif } PGRAPHGLState; diff --git a/hw/xbox/nv2a/pgraph/gl/reports.c b/hw/xbox/nv2a/pgraph/gl/reports.c index a7a23daa3a..2dea09e590 100644 --- a/hw/xbox/nv2a/pgraph/gl/reports.c +++ b/hw/xbox/nv2a/pgraph/gl/reports.c @@ -42,14 +42,6 @@ static void process_pending_report(NV2AState *d, QueryReport *report) for (int i = 0; i < report->query_count; i++) { GLuint gl_query_result = 0; glGetQueryObjectuiv(report->queries[i], GL_QUERY_RESULT, &gl_query_result); -#ifdef __ANDROID__ - if (r->supported_extensions.occlusion_query_boolean) { - /* GLES visibility queries only tell us whether any samples passed. - * Preserve that boolean signal instead of reporting a permanent 0. */ - r->zpass_pixel_count_result += gl_query_result ? 1 : 0; - continue; - } -#endif gl_query_result /= pg->surface_scale_factor * pg->surface_scale_factor; r->zpass_pixel_count_result += gl_query_result; } diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index 35e4829b15..bb81637387 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -45,45 +45,6 @@ static GLenum get_gl_primitive_mode(enum ShaderPrimitiveMode primitive_mode) } } -#ifdef __ANDROID__ -static void android_log_shader_stage_errors(const char *ctx) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at %s", err, ctx); - } -} - -static void android_log_apply_uniform_entry_errors(const char *uniform_set) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X before apply_uniform_updates:%s", - err, uniform_set); - } -} - -static void android_log_uniform_update_errors(const char *uniform_set, - const UniformInfo *info, - int loc) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print( - ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at apply_uniform_updates:%s.%s type=%s count=%zu " - "loc=%d", - err, uniform_set, info->name, - uniform_element_type_to_str[info->type], info->count, loc); - } -} -#endif - static void log_shader_source_with_line_numbers(const char *name, const char *code) { @@ -255,18 +216,13 @@ static void generate_shaders(PGRAPHGLState *r, ShaderBinding *binding) ShaderModuleCacheKey key; #ifdef __ANDROID__ const bool gles = true; - const int gles_version = r->gles_version; + const int gles_version = 320; #else const bool gles = false; const int gles_version = 0; #endif bool need_geometry_shader = pgraph_glsl_need_geom(&state->geom); -#ifdef __ANDROID__ - if (need_geometry_shader && !r->geometry_shaders_supported) { - need_geometry_shader = false; - } -#endif if (need_geometry_shader) { memset(&key, 0, sizeof(key)); key.kind = GL_GEOMETRY_SHADER; @@ -297,23 +253,9 @@ static void generate_shaders(PGRAPHGLState *r, ShaderBinding *binding) glLinkProgram(program); GLint linked = 0; glGetProgramiv(program, GL_LINK_STATUS, &linked); - binding->gl_primitive_mode = - get_gl_primitive_mode(state->geom.primitive_mode); if(!linked) { GLchar log[2048]; glGetProgramInfoLog(program, 2048, NULL, log); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "xemu-android", - "nv2a: shader linking failed: %s", log); - binding->gl_program = 0; - binding->initialized = true; - memset(binding->uniform_locs.vsh, 0xFF, - sizeof(binding->uniform_locs.vsh)); - memset(binding->uniform_locs.psh, 0xFF, - sizeof(binding->uniform_locs.psh)); - glDeleteProgram(program); - return; -#endif fprintf(stderr, "nv2a: shader linking failed: %s\n", log); abort(); } @@ -321,6 +263,8 @@ static void generate_shaders(PGRAPHGLState *r, ShaderBinding *binding) glUseProgram(program); binding->gl_program = program; + binding->gl_primitive_mode = + get_gl_primitive_mode(state->geom.primitive_mode); binding->initialized = true; set_texture_sampler_uniforms(binding); @@ -598,8 +542,6 @@ static void shader_cache_entry_init(Lru *lru, LruNode *node, const void *state) binding->cached = false; binding->program = NULL; binding->save_thread = NULL; - memset(binding->uniform_locs.vsh, 0xFF, sizeof(binding->uniform_locs.vsh)); - memset(binding->uniform_locs.psh, 0xFF, sizeof(binding->uniform_locs.psh)); } static void shader_cache_entry_post_evict(Lru *lru, LruNode *node) @@ -643,7 +585,7 @@ void pgraph_gl_init_shaders(PGRAPHState *pg) /* FIXME: Make this configurable */ const size_t shader_cache_size = 50*1024; - lru_init(&r->shader_cache, 1 << 16); + lru_init(&r->shader_cache, 1u << 16); r->shader_cache_entries = malloc(shader_cache_size * sizeof(ShaderBinding)); assert(r->shader_cache_entries != NULL); for (int i = 0; i < shader_cache_size; i++) { @@ -659,7 +601,7 @@ void pgraph_gl_init_shaders(PGRAPHState *pg) /* FIXME: Make this configurable */ const size_t shader_module_cache_size = 50*1024; - lru_init(&r->shader_module_cache, 1 << 16); + lru_init(&r->shader_module_cache, 1u << 16); r->shader_module_cache_entries = g_malloc_n(shader_module_cache_size, sizeof(ShaderModuleCacheEntry)); assert(r->shader_module_cache_entries != NULL); @@ -678,12 +620,10 @@ void pgraph_gl_finalize_shaders(PGRAPHState *pg) // Clear out shader cache pgraph_gl_shader_write_cache_reload_list(pg); // FIXME: also flushes, rename for clarity - lru_destroy(&r->shader_cache); free(r->shader_cache_entries); r->shader_cache_entries = NULL; lru_flush(&r->shader_module_cache); - lru_destroy(&r->shader_module_cache); g_free(r->shader_module_cache_entries); r->shader_module_cache_entries = NULL; @@ -794,14 +734,9 @@ void pgraph_gl_shader_cache_to_disk(ShaderBinding *binding) qemu_thread_create(binding->save_thread, name, shader_write_to_disk, binding, QEMU_THREAD_JOINABLE); } -static void apply_uniform_updates(const char *uniform_set, - const UniformInfo *info, int *locs, +static void apply_uniform_updates(const UniformInfo *info, int *locs, void *values, size_t count) { -#ifdef __ANDROID__ - android_log_apply_uniform_entry_errors(uniform_set); -#endif - for (int i = 0; i < count; i++) { if (locs[i] == -1) { continue; @@ -840,13 +775,13 @@ static void apply_uniform_updates(const char *uniform_set, default: g_assert_not_reached(); } - -#ifdef __ANDROID__ - android_log_uniform_update_errors(uniform_set, &info[i], locs[i]); -#endif } -#ifndef __ANDROID__ +#ifdef __ANDROID__ + while (glGetError() != GL_NO_ERROR) { + /* Ignore uniform update GL errors on Android. */ + } +#else assert(glGetError() == GL_NO_ERROR); #endif } @@ -860,7 +795,7 @@ static void update_shader_uniforms(PGRAPHState *pg, ShaderBinding *binding) VshUniformValues vsh_values; pgraph_glsl_set_vsh_uniform_values(pg, &binding->state.vsh, binding->uniform_locs.vsh, &vsh_values); - apply_uniform_updates("vsh", VshUniformInfo, binding->uniform_locs.vsh, + apply_uniform_updates(VshUniformInfo, binding->uniform_locs.vsh, &vsh_values, VshUniform__COUNT); PshUniformValues psh_values; @@ -872,7 +807,7 @@ static void update_shader_uniforms(PGRAPHState *pg, ShaderBinding *binding) psh_values.texScale[i] = scale; } } - apply_uniform_updates("psh", PshUniformInfo, binding->uniform_locs.psh, + apply_uniform_updates(PshUniformInfo, binding->uniform_locs.psh, &psh_values, PshUniform__COUNT); } @@ -918,10 +853,6 @@ void pgraph_gl_bind_shaders(PGRAPHState *pg) if (binding_changed) { nv2a_profile_inc_counter(NV2A_PROF_SHADER_BIND); glUseProgram(r->shader_binding->gl_program); -#ifdef __ANDROID__ - android_log_shader_stage_errors( - "pgraph_gl_bind_shaders: binding_changed"); -#endif } NV2A_GL_DGROUP_END(); @@ -929,10 +860,6 @@ void pgraph_gl_bind_shaders(PGRAPHState *pg) update_uniforms: assert(r->shader_binding); assert(r->shader_binding->initialized); - glUseProgram(r->shader_binding->gl_program); -#ifdef __ANDROID__ - android_log_shader_stage_errors("pgraph_gl_bind_shaders: update_uniforms"); -#endif update_shader_uniforms(pg, r->shader_binding); } diff --git a/hw/xbox/nv2a/pgraph/gl/surface.c b/hw/xbox/nv2a/pgraph/gl/surface.c index e42a76031f..e99c43a45c 100644 --- a/hw/xbox/nv2a/pgraph/gl/surface.c +++ b/hw/xbox/nv2a/pgraph/gl/surface.c @@ -28,121 +28,9 @@ #ifdef __ANDROID__ #include -#ifdef __aarch64__ -#include -#endif #endif #ifdef __ANDROID__ -extern bool xemu_android_is_debug_logging_enabled(void); - -static bool android_log_and_drain_gl_errors(const char *ctx) -{ - GLenum err; - bool had_error = false; - - if (!xemu_android_is_debug_logging_enabled()) { - return false; - } - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at %s", err, ctx); - had_error = true; - } - - return had_error; -} - -static bool android_log_surface_download_errors(const char *ctx, - const SurfaceBinding *surface) -{ - bool had_error = android_log_and_drain_gl_errors(ctx); - - if (!had_error || !surface) { - return had_error; - } - - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - " surface download: kind=%s attachment=0x%X format=0x%X " - "type=0x%X bpp=%u size=%ux%u pitch=%u addr=0x%llX", - surface->color ? "color" : "zeta", - surface->fmt.gl_attachment, - surface->fmt.gl_format, - surface->fmt.gl_type, - surface->fmt.bytes_per_pixel, - surface->width, - surface->height, - surface->pitch, - (unsigned long long)surface->vram_addr); - - return had_error; -} - -static bool android_drain_gl_errors_silent(void) -{ - GLenum err; - bool had_error = false; - - if (!xemu_android_is_debug_logging_enabled()) { - return false; - } - - while ((err = glGetError()) != GL_NO_ERROR) { - had_error = true; - } - - return had_error; -} - -static void android_glo_readpixels(PGRAPHGLState *r, GLenum gl_format, - GLenum gl_type, - unsigned int bytes_per_pixel, - unsigned int stride, - unsigned int width, - unsigned int height, bool vflip, - void *data) -{ - size_t required_size = stride * height; - glBindBuffer(GL_PIXEL_PACK_BUFFER, r->gl_download_pbo); - if (required_size > r->gl_download_pbo_size) { - glBufferData(GL_PIXEL_PACK_BUFFER, required_size, NULL, - GL_STREAM_READ); - r->gl_download_pbo_size = required_size; - } - - int rl, pa; - glGetIntegerv(GL_PACK_ROW_LENGTH, &rl); - glGetIntegerv(GL_PACK_ALIGNMENT, &pa); - glPixelStorei(GL_PACK_ROW_LENGTH, stride / bytes_per_pixel); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - glReadPixels(0, 0, width, height, gl_format, gl_type, 0); - - void *mapped_data = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, - required_size, GL_MAP_READ_BIT); - if (mapped_data) { - if (vflip) { - GLubyte *b = (GLubyte *) data; - GLubyte *c = - &((GLubyte *) mapped_data)[stride * (height - 1)]; - for (unsigned int irow = 0; irow < height; irow++) { - memcpy(b, c, width * bytes_per_pixel); - b += stride; - c -= stride; - } - } else { - memcpy(data, mapped_data, required_size); - } - glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - } - - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - - glPixelStorei(GL_PACK_ROW_LENGTH, rl); - glPixelStorei(GL_PACK_ALIGNMENT, pa); -} - static void android_sanitize_surface_format(PGRAPHGLState *r, SurfaceFormatInfo *fmt) { @@ -189,328 +77,6 @@ static uint8_t android_expand_5_to_8(uint8_t value) return (value << 3) | (value >> 2); } -#ifdef __aarch64__ -static const uint8_t android_neon_bgra_to_rgba_perm[16] = - {2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15}; -static const uint8_t android_neon_b8g8r8a8_to_rgba_perm[16] = - {1,2,3,0, 5,6,7,4, 9,10,11,8, 13,14,15,12}; -static const uint8_t android_neon_r8g8b8a8_to_rgba_perm[16] = - {3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12}; -static const uint8_t android_neon_force_alpha_mask[16] = - {0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF}; - -static inline void android_neon_shuffle_row_4bpp(const uint8_t *src_row, - uint8_t *dst_row, - unsigned int width, - const uint8_t perm_arr[16], - bool force_opaque_alpha) -{ - uint8x16_t vperm = vld1q_u8(perm_arr); - uint8x16_t valpha_mask = force_opaque_alpha - ? vld1q_u8(android_neon_force_alpha_mask) - : vdupq_n_u8(0); - const uint8_t *src = src_row; - uint8_t *dst = dst_row; - unsigned int remaining = width; - - while (remaining >= 4) { - uint8x16_t v = vqtbl1q_u8(vld1q_u8(src), vperm); - vst1q_u8(dst, vorrq_u8(v, valpha_mask)); - src += 16; - dst += 16; - remaining -= 4; - } - - while (remaining-- > 0) { - dst[0] = src[perm_arr[0]]; - dst[1] = src[perm_arr[1]]; - dst[2] = src[perm_arr[2]]; - dst[3] = force_opaque_alpha ? 0xFF : src[perm_arr[3]]; - src += 4; - dst += 4; - } -} - -static inline void android_neon_a1x1r5g5b5_to_rgba8_row( - const uint8_t *src_row, - uint8_t *dst_row, - unsigned int width, - bool preserve_alpha) -{ - const uint16x8_t mask_5 = vdupq_n_u16(0x1F); - unsigned int remaining = width; - const uint16_t *src = (const uint16_t *)src_row; - uint8_t *dst = dst_row; - - while (remaining >= 8) { - uint16x8_t pixels = vld1q_u16(src); - uint16x8_t r5 = vandq_u16(vshrq_n_u16(pixels, 10), mask_5); - uint16x8_t g5 = vandq_u16(vshrq_n_u16(pixels, 5), mask_5); - uint16x8_t b5 = vandq_u16(pixels, mask_5); - uint8x8_t r8 = vmovn_u16(vorrq_u16(vshlq_n_u16(r5, 3), - vshrq_n_u16(r5, 2))); - uint8x8_t g8 = vmovn_u16(vorrq_u16(vshlq_n_u16(g5, 3), - vshrq_n_u16(g5, 2))); - uint8x8_t b8 = vmovn_u16(vorrq_u16(vshlq_n_u16(b5, 3), - vshrq_n_u16(b5, 2))); - uint8x8_t a8 = preserve_alpha - ? vmovn_u16(vceqq_u16(vshrq_n_u16(pixels, 15), vdupq_n_u16(1))) - : vdup_n_u8(0xFF); - uint8x8x4_t rgba = { { r8, g8, b8, a8 } }; - - vst4_u8(dst, rgba); - src += 8; - dst += 32; - remaining -= 8; - } - - while (remaining-- > 0) { - uint16_t pixel = *src++; - dst[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); - dst[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); - dst[2] = android_expand_5_to_8(pixel & 0x1F); - dst[3] = preserve_alpha ? ((pixel & 0x8000) ? 0xFF : 0x00) : 0xFF; - dst += 4; - } -} - -static inline void android_neon_r5g6b5_to_rgba8_row(const uint8_t *src_row, - uint8_t *dst_row, - unsigned int width) -{ - const uint16x8_t mask_5 = vdupq_n_u16(0x1F); - const uint16x8_t mask_6 = vdupq_n_u16(0x3F); - unsigned int remaining = width; - const uint16_t *src = (const uint16_t *)src_row; - uint8_t *dst = dst_row; - - while (remaining >= 8) { - uint16x8_t pixels = vld1q_u16(src); - uint16x8_t r5 = vandq_u16(vshrq_n_u16(pixels, 11), mask_5); - uint16x8_t g6 = vandq_u16(vshrq_n_u16(pixels, 5), mask_6); - uint16x8_t b5 = vandq_u16(pixels, mask_5); - uint8x8_t r8 = vmovn_u16(vorrq_u16(vshlq_n_u16(r5, 3), - vshrq_n_u16(r5, 2))); - uint8x8_t g8 = vmovn_u16(vorrq_u16(vshlq_n_u16(g6, 2), - vshrq_n_u16(g6, 4))); - uint8x8_t b8 = vmovn_u16(vorrq_u16(vshlq_n_u16(b5, 3), - vshrq_n_u16(b5, 2))); - uint8x8x4_t rgba = { { r8, g8, b8, vdup_n_u8(0xFF) } }; - - vst4_u8(dst, rgba); - src += 8; - dst += 32; - remaining -= 8; - } - - while (remaining-- > 0) { - uint16_t pixel = *src++; - dst[0] = android_expand_5_to_8((pixel >> 11) & 0x1F); - dst[1] = (uint8_t)(((pixel >> 5) & 0x3F) * 255 / 63); - dst[2] = android_expand_5_to_8(pixel & 0x1F); - dst[3] = 0xFF; - dst += 4; - } -} - -static inline void android_neon_rgba8_to_x1r5g5b5_row(const uint8_t *src_row, - uint8_t *dst_row, - unsigned int width) -{ - unsigned int remaining = width; - const uint8_t *src = src_row; - uint16_t *dst = (uint16_t *)dst_row; - - while (remaining >= 8) { - uint8x8x4_t rgba = vld4_u8(src); - uint16x8_t r = vshlq_n_u16(vmovl_u8(vshr_n_u8(rgba.val[0], 3)), 10); - uint16x8_t g = vshlq_n_u16(vmovl_u8(vshr_n_u8(rgba.val[1], 3)), 5); - uint16x8_t b = vmovl_u8(vshr_n_u8(rgba.val[2], 3)); - uint16x8_t packed = - vorrq_u16(vdupq_n_u16(0x8000), vorrq_u16(r, vorrq_u16(g, b))); - - vst1q_u16(dst, packed); - src += 32; - dst += 8; - remaining -= 8; - } - - while (remaining-- > 0) { - uint16_t packed = - 0x8000 | - ((uint16_t)(src[0] >> 3) << 10) | - ((uint16_t)(src[1] >> 3) << 5) | - (uint16_t)(src[2] >> 3); - *dst++ = packed; - src += 4; - } -} - -static inline bool android_neon_surface_copy_shrink_row_4bpp( - uint8_t *out, - const uint8_t *in, - unsigned int width, - unsigned int factor) -{ - uint32_t *dst = (uint32_t *)out; - const uint32_t *src = (const uint32_t *)in; - unsigned int remaining = width; - - switch (factor) { - case 2: - while (remaining >= 4) { - uint32x4x2_t pixels = vld2q_u32(src); - vst1q_u32(dst, pixels.val[0]); - src += 8; - dst += 4; - remaining -= 4; - } - break; - case 3: - while (remaining >= 4) { - uint32x4x3_t pixels = vld3q_u32(src); - vst1q_u32(dst, pixels.val[0]); - src += 12; - dst += 4; - remaining -= 4; - } - break; - case 4: - while (remaining >= 4) { - uint32x4x4_t pixels = vld4q_u32(src); - vst1q_u32(dst, pixels.val[0]); - src += 16; - dst += 4; - remaining -= 4; - } - break; - default: - return false; - } - - out = (uint8_t *)dst; - in = (const uint8_t *)src; - while (remaining-- > 0) { - *(uint32_t *)out = *(const uint32_t *)in; - out += 4; - in += 4 * factor; - } - - return true; -} - -static inline bool android_neon_surface_copy_shrink_row_2bpp( - uint8_t *out, - const uint8_t *in, - unsigned int width, - unsigned int factor) -{ - uint16_t *dst = (uint16_t *)out; - const uint16_t *src = (const uint16_t *)in; - unsigned int remaining = width; - - switch (factor) { - case 2: - while (remaining >= 8) { - uint16x8x2_t pixels = vld2q_u16(src); - vst1q_u16(dst, pixels.val[0]); - src += 16; - dst += 8; - remaining -= 8; - } - break; - case 3: - while (remaining >= 8) { - uint16x8x3_t pixels = vld3q_u16(src); - vst1q_u16(dst, pixels.val[0]); - src += 24; - dst += 8; - remaining -= 8; - } - break; - case 4: - while (remaining >= 8) { - uint16x8x4_t pixels = vld4q_u16(src); - vst1q_u16(dst, pixels.val[0]); - src += 32; - dst += 8; - remaining -= 8; - } - break; - default: - return false; - } - - out = (uint8_t *)dst; - in = (const uint8_t *)src; - while (remaining-- > 0) { - *(uint16_t *)out = *(const uint16_t *)in; - out += 2; - in += 2 * factor; - } - - return true; -} - -static inline void android_neon_pack_depth16_row_to_guest(const uint8_t *src_row, - uint8_t *dst_row, - unsigned int width) -{ - unsigned int remaining = width; - - while (remaining >= 8) { - uint8x8x4_t rgba = vld4_u8(src_row); - uint8x8x2_t depth16 = { { rgba.val[0], rgba.val[1] } }; - - vst2_u8(dst_row, depth16); - src_row += 32; - dst_row += 16; - remaining -= 8; - } - - while (remaining-- > 0) { - dst_row[0] = src_row[0]; - dst_row[1] = src_row[1]; - src_row += 4; - dst_row += 2; - } -} - -static inline void android_neon_pack_z24s8_row_to_guest( - const uint8_t *depth_row, - const uint8_t *stencil_row, - uint8_t *dst_row, - unsigned int width) -{ - unsigned int remaining = width; - - while (remaining >= 8) { - uint8x8x4_t depth = vld4_u8(depth_row); - uint8x8x4_t z24s8 = { { - vld1_u8(stencil_row), - depth.val[0], - depth.val[1], - depth.val[2], - } }; - - vst4_u8(dst_row, z24s8); - depth_row += 32; - stencil_row += 8; - dst_row += 32; - remaining -= 8; - } - - while (remaining-- > 0) { - dst_row[0] = *stencil_row++; - dst_row[1] = depth_row[0]; - dst_row[2] = depth_row[1]; - dst_row[3] = depth_row[2]; - depth_row += 4; - dst_row += 4; - } -} -#endif - static void android_surface_guest_to_rgba8(const SurfaceBinding *surface, const uint8_t *src, unsigned int width, @@ -536,50 +102,22 @@ static void android_surface_guest_to_rgba8(const SurfaceBinding *surface, break; case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: - { - bool preserve_alpha = (surface->shape.color_format == - NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8); for (y = 0; y < height; y++) { const uint8_t *src_row = src + y * src_stride; uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - /* vqtbl1q_u8: 16-byte shuffle, processes 4 pixels per instruction. - * Permutation swaps R (byte 2) and B (byte 0) within each pixel. */ - static const uint8_t perm_arr[16] = - {2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15}; - uint8x16_t vperm = vld1q_u8(perm_arr); - /* For X8R8G8B8, force alpha=0xFF by ORing a pre-built mask. */ - static const uint8_t alpha_mask_arr[16] = - {0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF}; - uint8x16_t valpha_mask = preserve_alpha - ? vdupq_n_u8(0) - : vld1q_u8(alpha_mask_arr); - unsigned int px = width; - while (px >= 4) { - uint8x16_t v = vqtbl1q_u8(vld1q_u8(src_row), vperm); - vst1q_u8(dst_row, vorrq_u8(v, valpha_mask)); - src_row += 16; dst_row += 16; px -= 4; - } - /* scalar tail for widths not divisible by 4 */ - while (px-- > 0) { - dst_row[0] = src_row[2]; - dst_row[1] = src_row[1]; - dst_row[2] = src_row[0]; - dst_row[3] = preserve_alpha ? src_row[3] : 0xFF; - src_row += 4; dst_row += 4; - } -#else for (x = 0; x < width; x++) { const uint8_t *pixel = src_row + x * 4; dst_row[x * 4 + 0] = pixel[2]; dst_row[x * 4 + 1] = pixel[1]; dst_row[x * 4 + 2] = pixel[0]; - dst_row[x * 4 + 3] = preserve_alpha ? pixel[3] : 0xFF; + dst_row[x * 4 + 3] = + (surface->shape.color_format == + NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8) + ? pixel[3] + : 0xFF; } -#endif } break; - } default: g_assert_not_reached(); } @@ -622,143 +160,90 @@ static void android_surface_guest_to_texture_rgba8( { unsigned int x, y; - switch (shape->color_format) { - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5: - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - android_neon_a1x1r5g5b5_to_rgba8_row(src_row, dst_row, width, true); -#else - for (x = 0; x < width; x++) { + for (y = 0; y < height; y++) { + const uint8_t *src_row = src + y * src_stride; + uint8_t *dst_row = dst + y * width * 4; + + for (x = 0; x < width; x++) { + uint8_t *out = dst_row + x * 4; + + switch (shape->color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5: { uint16_t pixel = lduw_le_p(src_row + x * 2); - uint8_t *out = dst_row + x * 4; out[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); out[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); out[2] = android_expand_5_to_8(pixel & 0x1F); out[3] = (pixel & 0x8000) ? 0xFF : 0x00; + break; } -#endif - } - break; - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5: - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - android_neon_a1x1r5g5b5_to_rgba8_row(src_row, dst_row, width, false); -#else - for (x = 0; x < width; x++) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5: { uint16_t pixel = lduw_le_p(src_row + x * 2); - uint8_t *out = dst_row + x * 4; out[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); out[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); out[2] = android_expand_5_to_8(pixel & 0x1F); out[3] = 0xFF; + break; } -#endif - } - break; - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - android_neon_r5g6b5_to_rgba8_row(src_row, dst_row, width); -#else - for (x = 0; x < width; x++) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: { uint16_t pixel = lduw_le_p(src_row + x * 2); - uint8_t *out = dst_row + x * 4; out[0] = android_expand_5_to_8((pixel >> 11) & 0x1F); out[1] = (uint8_t)(((pixel >> 5) & 0x3F) * 255 / 63); out[2] = android_expand_5_to_8(pixel & 0x1F); out[3] = 0xFF; + break; } -#endif - } - break; - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: - { - bool force_opaque_alpha = - (shape->color_format == NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8 || - shape->color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8); - - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - android_neon_shuffle_row_4bpp(src_row, dst_row, width, - android_neon_bgra_to_rgba_perm, - force_opaque_alpha); -#else - for (x = 0; x < width; x++) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: { const uint8_t *pixel = src_row + x * 4; - uint8_t *out = dst_row + x * 4; out[0] = pixel[2]; out[1] = pixel[1]; out[2] = pixel[0]; - out[3] = force_opaque_alpha ? 0xFF : pixel[3]; + out[3] = pixel[3]; + break; } -#endif - } - break; - } - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: - for (y = 0; y < height; y++) { - memcpy(dst + y * width * 4, src + y * src_stride, width * 4); - } - break; - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - android_neon_shuffle_row_4bpp(src_row, dst_row, width, - android_neon_b8g8r8a8_to_rgba_perm, - false); -#else - for (x = 0; x < width; x++) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[2]; + out[1] = pixel[1]; + out[2] = pixel[0]; + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[0]; + out[1] = pixel[1]; + out[2] = pixel[2]; + out[3] = pixel[3]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: { const uint8_t *pixel = src_row + x * 4; - uint8_t *out = dst_row + x * 4; out[0] = pixel[1]; out[1] = pixel[2]; out[2] = pixel[3]; out[3] = pixel[0]; + break; } -#endif - } - break; - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * width * 4; -#ifdef __aarch64__ - android_neon_shuffle_row_4bpp(src_row, dst_row, width, - android_neon_r8g8b8a8_to_rgba_perm, - false); -#else - for (x = 0; x < width; x++) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: { const uint8_t *pixel = src_row + x * 4; - uint8_t *out = dst_row + x * 4; out[0] = pixel[3]; out[1] = pixel[2]; out[2] = pixel[1]; out[3] = pixel[0]; + break; + } + default: + g_assert_not_reached(); } -#endif } - break; - default: - g_assert_not_reached(); } } @@ -777,9 +262,6 @@ static void android_surface_rgba8_to_guest(const SurfaceBinding *surface, for (y = 0; y < height; y++) { const uint8_t *src_row = src + y * src_stride; uint8_t *dst_row = dst + y * dst_stride; -#ifdef __aarch64__ - android_neon_rgba8_to_x1r5g5b5_row(src_row, dst_row, width); -#else for (x = 0; x < width; x++) { const uint8_t *pixel = src_row + x * 4; uint16_t packed = @@ -789,23 +271,13 @@ static void android_surface_rgba8_to_guest(const SurfaceBinding *surface, (uint16_t)(pixel[2] >> 3); stw_le_p(dst_row + x * 2, packed); } -#endif } break; case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: - { - bool force_opaque_alpha = - (surface->shape.color_format != - NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8); for (y = 0; y < height; y++) { const uint8_t *src_row = src + y * src_stride; uint8_t *dst_row = dst + y * dst_stride; -#ifdef __aarch64__ - android_neon_shuffle_row_4bpp(src_row, dst_row, width, - android_neon_bgra_to_rgba_perm, - force_opaque_alpha); -#else for (x = 0; x < width; x++) { const uint8_t *pixel = src_row + x * 4; uint8_t *out = dst_row + x * 4; @@ -818,10 +290,8 @@ static void android_surface_rgba8_to_guest(const SurfaceBinding *surface, ? pixel[3] : 0xFF; } -#endif } break; - } default: g_assert_not_reached(); } @@ -1009,37 +479,11 @@ static void init_render_to_texture(PGRAPHState *pg) " vec2 texCoord = gl_FragCoord.xy / texSize;\n" " out_Color.rgba = texture(tex, texCoord);\n" "}\n"; -#ifdef __ANDROID__ - const char *depth_fs = - "#version 300 es\n" - "precision highp float;\n" - "precision highp int;\n" - "precision highp sampler2D;\n" - "uniform sampler2D tex;\n" - "uniform float depth_scale;\n" - "layout(location = 0) out vec4 out_Color;\n" - "void main()\n" - "{\n" - " float depth = texelFetch(tex, ivec2(gl_FragCoord.xy), 0).r;\n" - " uint packed = uint(clamp(depth * depth_scale + 0.5, 0.0, depth_scale));\n" - " out_Color = vec4(float(packed & 0xFFu) / 255.0,\n" - " float((packed >> 8) & 0xFFu) / 255.0,\n" - " float((packed >> 16) & 0xFFu) / 255.0,\n" - " 1.0);\n" - "}\n"; -#endif r->s2t_rndr.prog = pgraph_gl_compile_shader(vs, fs); r->s2t_rndr.tex_loc = glGetUniformLocation(r->s2t_rndr.prog, "tex"); r->s2t_rndr.surface_size_loc = glGetUniformLocation(r->s2t_rndr.prog, "surface_size"); -#ifdef __ANDROID__ - r->s2t_rndr.depth_prog = pgraph_gl_compile_shader(vs, depth_fs); - r->s2t_rndr.depth_tex_loc = - glGetUniformLocation(r->s2t_rndr.depth_prog, "tex"); - r->s2t_rndr.depth_scale_loc = - glGetUniformLocation(r->s2t_rndr.depth_prog, "depth_scale"); -#endif glGenVertexArrays(1, &r->s2t_rndr.vao); glBindVertexArray(r->s2t_rndr.vao); @@ -1055,10 +499,6 @@ static void finalize_render_to_texture(PGRAPHState *pg) glDeleteProgram(r->s2t_rndr.prog); r->s2t_rndr.prog = 0; -#ifdef __ANDROID__ - glDeleteProgram(r->s2t_rndr.depth_prog); - r->s2t_rndr.depth_prog = 0; -#endif glDeleteVertexArrays(1, &r->s2t_rndr.vao); r->s2t_rndr.vao = 0; @@ -1170,7 +610,7 @@ static bool render_surface_to(NV2AState *d, SurfaceBinding *surface, #ifndef __ANDROID__ assert(glGetError() == GL_NO_ERROR); #else - if (android_log_and_drain_gl_errors("render_surface_to: fbo setup")) { + if (glGetError() != GL_NO_ERROR) { glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); return false; } @@ -1179,29 +619,21 @@ static bool render_surface_to(NV2AState *d, SurfaceBinding *surface, float color[] = { 0.0f, 0.0f, 0.0f, 0.0f }; glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); #ifdef __ANDROID__ - if (r->supported_extensions.texture_border_clamp) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, NV2A_GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, NV2A_GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, NV2A_GL_TEXTURE_BORDER_COLOR, color); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, NV2A_GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, NV2A_GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, NV2A_GL_TEXTURE_BORDER_COLOR, color); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); #endif glBindVertexArray(r->s2t_rndr.vao); glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo); glUseProgram(r->s2t_rndr.prog); - if (r->s2t_rndr.tex_loc >= 0) { - glUniform1i(r->s2t_rndr.tex_loc, texture_unit); - } - if (r->s2t_rndr.surface_size_loc >= 0) { - glUniform2f(r->s2t_rndr.surface_size_loc, width, height); - } + glProgramUniform1i(r->s2t_rndr.prog, r->s2t_rndr.tex_loc, + texture_unit); + glProgramUniform2f(r->s2t_rndr.prog, + r->s2t_rndr.surface_size_loc, width, height); glViewport(0, 0, width, height); glColorMask(true, true, true, true); @@ -1218,18 +650,6 @@ static bool render_surface_to(NV2AState *d, SurfaceBinding *surface, glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); -#ifdef __ANDROID__ - if (android_log_and_drain_gl_errors("render_surface_to: draw")) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_target, - 0, 0); - glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); - glBindVertexArray(r->gl_vertex_array); - glBindTexture(gl_target, gl_texture); - glUseProgram(r->shader_binding ? r->shader_binding->gl_program : 0); - return false; - } -#endif - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_target, 0, 0); glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); @@ -1294,24 +714,20 @@ static void render_surface_to_texture_slow(NV2AState *d, #ifdef __ANDROID__ if (android_surface_to_texture_rgba8_compatible(surface, texture_shape)) { - PGRAPHGLState *r = pg->gl_renderer_state; - size_t needed = (size_t)width * height * 4; - if (needed > r->android_s2t_conv_buf_size) { - r->android_s2t_conv_buf = g_realloc(r->android_s2t_conv_buf, needed); - r->android_s2t_conv_buf_size = needed; - } + uint8_t *upload_tmp = g_malloc(width * height * 4); if (android_surface_to_texture_needs_guest_reinterpretation( surface, texture_shape)) { android_surface_guest_to_texture_rgba8( texture_shape, buf, width, height, - width * surface->fmt.bytes_per_pixel, r->android_s2t_conv_buf); + width * surface->fmt.bytes_per_pixel, upload_tmp); } else { android_surface_guest_to_rgba8(surface, buf, width, height, width * surface->fmt.bytes_per_pixel, - r->android_s2t_conv_buf); + upload_tmp); } glTexImage2D(texture->gl_target, 0, GL_RGBA8, width, height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, r->android_s2t_conv_buf); + GL_RGBA, GL_UNSIGNED_BYTE, upload_tmp); + g_free(upload_tmp); } else #endif glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0, @@ -1669,16 +1085,6 @@ static void bind_current_surface(NV2AState *d) PGRAPHState *pg = &d->pgraph; PGRAPHGLState *r = pg->gl_renderer_state; - /* Clear any temporary download/render attachments before restoring - * the active render targets. - */ - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - 0, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - if (r->color_binding) { glFramebufferTexture2D(GL_FRAMEBUFFER, r->color_binding->fmt.gl_attachment, GL_TEXTURE_2D, r->color_binding->gl_buffer, 0); @@ -1738,36 +1144,20 @@ static void bind_current_surface(NV2AState *d) } } -static void surface_copy_shrink_row(uint8_t *out, const uint8_t *in, +static void surface_copy_shrink_row(uint8_t *out, uint8_t *in, unsigned int width, unsigned int bytes_per_pixel, unsigned int factor) { - if (factor == 1) { - memcpy(out, in, width * bytes_per_pixel); - return; - } - -#ifdef __aarch64__ - if (bytes_per_pixel == 4 && - android_neon_surface_copy_shrink_row_4bpp(out, in, width, factor)) { - return; - } - if (bytes_per_pixel == 2 && - android_neon_surface_copy_shrink_row_2bpp(out, in, width, factor)) { - return; - } -#endif - if (bytes_per_pixel == 4) { for (unsigned int x = 0; x < width; x++) { - *(uint32_t *)out = *(const uint32_t *)in; + *(uint32_t *)out = *(uint32_t *)in; out += 4; in += 4 * factor; } } else if (bytes_per_pixel == 2) { for (unsigned int x = 0; x < width; x++) { - *(uint16_t *)out = *(const uint16_t *)in; + *(uint16_t *)out = *(uint16_t *)in; out += 2; in += 2 * factor; } @@ -1780,366 +1170,12 @@ static void surface_copy_shrink_row(uint8_t *out, const uint8_t *in, } } -#ifdef __ANDROID__ -static bool android_surface_download_depth16_to_guest(NV2AState *d, - SurfaceBinding *surface, - bool swizzle, bool flip, - bool downscale, - uint8_t *pixels) -{ - PGRAPHState *pg = &d->pgraph; - PGRAPHGLState *r = pg->gl_renderer_state; - const unsigned int factor = downscale ? pg->surface_scale_factor : 1; - const unsigned int read_width = surface->width * factor; - const unsigned int read_height = surface->height * factor; - const unsigned int rgba_stride = read_width * 4; - GLuint pack_texture = 0; - uint8_t *rgba_pixels = NULL; - uint8_t *rgba_linear = NULL; - uint8_t *linear_guest = pixels; - uint8_t *swizzle_buf = NULL; - bool ok = false; - - if (surface->color || surface->fmt.gl_attachment != GL_DEPTH_ATTACHMENT || - surface->fmt.gl_format != GL_DEPTH_COMPONENT || - surface->fmt.gl_type != GL_UNSIGNED_SHORT || - r->s2t_rndr.depth_prog == 0) { - return false; - } - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glGenTextures(1, &pack_texture); - glBindTexture(GL_TEXTURE_2D, pack_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, read_width, read_height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, NULL); - - glBindFramebuffer(GL_FRAMEBUFFER, r->s2t_rndr.fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - pack_texture, 0); - { - GLenum draw_buffers[1] = { GL_COLOR_ATTACHMENT0 }; - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - goto cleanup; - } - glDrawBuffers(1, draw_buffers); - glReadBuffer(GL_COLOR_ATTACHMENT0); - } - - if (android_log_surface_download_errors( - "surface_download_depth16: fbo setup", surface)) { - goto cleanup; - } - - glBindVertexArray(r->s2t_rndr.vao); - glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo); - glUseProgram(r->s2t_rndr.depth_prog); - if ((GLint)r->s2t_rndr.depth_tex_loc >= 0) { - glUniform1i(r->s2t_rndr.depth_tex_loc, 0); - } - if (r->s2t_rndr.depth_scale_loc >= 0) { - glUniform1f(r->s2t_rndr.depth_scale_loc, 65535.0f); - } - glViewport(0, 0, read_width, read_height); - glColorMask(true, true, true, true); - glDisable(GL_DITHER); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_BLEND); - glDisable(GL_STENCIL_TEST); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, 3); - - if (android_log_surface_download_errors( - "surface_download_depth16: pack draw", surface)) { - goto cleanup; - } - - rgba_pixels = g_malloc(read_height * rgba_stride); - android_glo_readpixels(r, GL_RGBA, GL_UNSIGNED_BYTE, 4, rgba_stride, - read_width, read_height, flip, rgba_pixels); - - if (android_log_surface_download_errors( - "surface_download_depth16: pack read", surface)) { - goto cleanup; - } - - rgba_linear = rgba_pixels; - if (downscale) { - rgba_linear = g_malloc(surface->width * surface->height * 4); - for (unsigned int y = 0; y < surface->height; y++) { - surface_copy_shrink_row(rgba_linear + y * surface->width * 4, - rgba_pixels + y * rgba_stride * factor, - surface->width, 4, factor); - } - } - - if (swizzle) { - swizzle_buf = g_malloc(surface->size); - linear_guest = swizzle_buf; - } - - for (unsigned int y = 0; y < surface->height; y++) { - const uint8_t *src_row = rgba_linear + y * surface->width * 4; - uint8_t *dst_row = linear_guest + y * surface->pitch; - -#ifdef __aarch64__ - android_neon_pack_depth16_row_to_guest(src_row, dst_row, surface->width); -#else - for (unsigned int x = 0; x < surface->width; x++) { - const uint8_t *src = src_row + x * 4; - stw_le_p(dst_row + x * 2, src[0] | (src[1] << 8)); - } -#endif - } - - if (swizzle) { - swizzle_rect(swizzle_buf, surface->width, surface->height, pixels, - surface->pitch, surface->fmt.bytes_per_pixel); - } - - ok = true; - -cleanup: - if (swizzle_buf) { - g_free(swizzle_buf); - } - if (rgba_linear && rgba_linear != rgba_pixels) { - g_free(rgba_linear); - } - if (rgba_pixels) { - g_free(rgba_pixels); - } - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - 0, 0); - if (pack_texture) { - glDeleteTextures(1, &pack_texture); - } - glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); - glBindVertexArray(r->gl_vertex_array); - glUseProgram(r->shader_binding ? r->shader_binding->gl_program : 0); - - return ok; -} - -static bool android_surface_download_z24s8_to_guest(NV2AState *d, - SurfaceBinding *surface, - bool swizzle, bool flip, - bool downscale, - uint8_t *pixels) -{ - PGRAPHState *pg = &d->pgraph; - PGRAPHGLState *r = pg->gl_renderer_state; - const unsigned int scale = pg->surface_scale_factor; - const unsigned int read_width = surface->width * scale; - const unsigned int read_height = surface->height * scale; - const unsigned int output_width = downscale ? surface->width : read_width; - const unsigned int output_height = downscale ? surface->height : read_height; - const unsigned int output_pitch = downscale ? surface->pitch - : surface->pitch * scale; - GLuint pack_texture = 0; - uint8_t *depth_pixels = NULL; - uint8_t *stencil_pixels = NULL; - uint8_t *linear_guest = pixels; - uint8_t *swizzle_buf = NULL; - GLint prev_read_buffer = GL_NONE; - bool ok = false; - static bool warned_stencil_readback_unsupported = false; - - if (surface->color || - surface->fmt.gl_attachment != GL_DEPTH_STENCIL_ATTACHMENT || - surface->fmt.gl_format != GL_DEPTH_STENCIL || - surface->fmt.gl_type != GL_UNSIGNED_INT_24_8) { - return false; - } - - glGetIntegerv(GL_READ_BUFFER, &prev_read_buffer); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glGenTextures(1, &pack_texture); - glBindTexture(GL_TEXTURE_2D, pack_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, read_width, read_height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, NULL); - - glBindFramebuffer(GL_FRAMEBUFFER, r->s2t_rndr.fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - pack_texture, 0); - { - GLenum draw_buffers[1] = { GL_COLOR_ATTACHMENT0 }; - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - goto cleanup; - } - glDrawBuffers(1, draw_buffers); - glReadBuffer(GL_COLOR_ATTACHMENT0); - } - - if (android_log_surface_download_errors( - "surface_download_z24s8: pack_fbo_setup", surface)) { - goto cleanup; - } - - glBindVertexArray(r->s2t_rndr.vao); - glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo); - glUseProgram(r->s2t_rndr.depth_prog); - if ((GLint)r->s2t_rndr.depth_tex_loc >= 0) { - glUniform1i(r->s2t_rndr.depth_tex_loc, 0); - } - if (r->s2t_rndr.depth_scale_loc >= 0) { - glUniform1f(r->s2t_rndr.depth_scale_loc, 16777215.0f); - } - glViewport(0, 0, read_width, read_height); - glColorMask(true, true, true, true); - glDisable(GL_DITHER); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_BLEND); - glDisable(GL_STENCIL_TEST); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, 3); - - if (android_log_surface_download_errors( - "surface_download_z24s8: pack_draw", surface)) { - goto cleanup; - } - - depth_pixels = g_malloc(read_width * read_height * 4); - android_glo_readpixels(r, GL_RGBA, GL_UNSIGNED_BYTE, 4, - read_width * 4, read_width, read_height, flip, - depth_pixels); - if (android_log_surface_download_errors( - "surface_download_z24s8: post-read-depth-pack", surface)) { - goto cleanup; - } - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - 0, 0); - glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); - glBindVertexArray(r->gl_vertex_array); - glUseProgram(r->shader_binding ? r->shader_binding->gl_program : 0); - - glReadBuffer(GL_NONE); - if (android_log_surface_download_errors( - "surface_download_z24s8: pre-read-stencil", surface)) { - goto cleanup; - } - - stencil_pixels = g_malloc0(read_width * read_height); - android_glo_readpixels(r, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, 1, - read_width, read_width, read_height, flip, - stencil_pixels); - if (android_drain_gl_errors_silent()) { - if (!warned_stencil_readback_unsupported) { - warned_stencil_readback_unsupported = true; - __android_log_print( - ANDROID_LOG_WARN, "xemu-android", - "surface_download_z24s8: stencil readback unsupported on this " - "GLES driver, substituting zero stencil"); - } - } - - if (swizzle) { - swizzle_buf = g_malloc(output_pitch * output_height); - linear_guest = swizzle_buf; - } - - for (unsigned int y = 0; y < output_height; y++) { - uint8_t *dst_row = linear_guest + y * output_pitch; - const unsigned int src_y = downscale ? y * scale : y; - -#ifdef __aarch64__ - if (!downscale) { - android_neon_pack_z24s8_row_to_guest( - depth_pixels + src_y * read_width * 4, - stencil_pixels + src_y * read_width, - dst_row, output_width); - continue; - } -#endif - - for (unsigned int x = 0; x < output_width; x++) { - const unsigned int src_x = downscale ? x * scale : x; - const unsigned int src_idx = src_y * read_width + src_x; - const uint8_t *src = depth_pixels + src_idx * 4; - uint32_t depth24 = src[0] | (src[1] << 8) | (src[2] << 16); - - stl_le_p((uint32_t *)(dst_row + x * 4), - (depth24 << 8) | stencil_pixels[src_idx]); - } - } - - if (swizzle) { - swizzle_rect(swizzle_buf, output_width, output_height, pixels, - output_pitch, surface->fmt.bytes_per_pixel); - } - - ok = true; - -cleanup: - if (swizzle_buf) { - g_free(swizzle_buf); - } - if (stencil_pixels) { - g_free(stencil_pixels); - } - if (depth_pixels) { - g_free(depth_pixels); - } - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - 0, 0); - if (pack_texture) { - glDeleteTextures(1, &pack_texture); - } - glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); - glBindVertexArray(r->gl_vertex_array); - glUseProgram(r->shader_binding ? r->shader_binding->gl_program : 0); - - glReadBuffer((GLenum)prev_read_buffer); - android_log_surface_download_errors( - "surface_download_z24s8: cleanup_restore_read_buffer", surface); - - return ok; -} -#endif - static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, bool swizzle, bool flip, bool downscale, uint8_t *pixels) { PGRAPHState *pg = &d->pgraph; bool ok = true; -#ifdef __ANDROID__ - GLint prev_read_buffer = GL_COLOR_ATTACHMENT0; - bool restore_read_buffer = false; -#endif swizzle &= surface->swizzle; downscale &= (pg->surface_scale_factor != 1); @@ -2196,24 +1232,8 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, goto cleanup; } -#ifdef __ANDROID__ - if (android_surface_download_depth16_to_guest(d, surface, swizzle, flip, - downscale, pixels)) { - goto cleanup; - } - if (android_surface_download_z24s8_to_guest(d, surface, swizzle, flip, - downscale, pixels)) { - goto cleanup; - } -#endif - /* Read surface into memory */ #ifdef __ANDROID__ - glGetIntegerv(GL_READ_BUFFER, &prev_read_buffer); - glReadBuffer(surface->color ? surface->fmt.gl_attachment : GL_NONE); - restore_read_buffer = true; - android_log_surface_download_errors("surface_download_to_buffer: pre-read", - surface); if (android_surface_uses_rgba8_transfer(surface)) { const unsigned int factor = downscale ? pg->surface_scale_factor : 1; const unsigned int read_width = surface->width * factor; @@ -2223,11 +1243,8 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, uint8_t *rgba_linear = rgba_pixels; uint8_t *linear_guest = pixels; - android_glo_readpixels(pg->gl_renderer_state, GL_RGBA, - GL_UNSIGNED_BYTE, 4, rgba_stride, - read_width, read_height, flip, rgba_pixels); - android_log_surface_download_errors( - "surface_download_to_buffer: post-read_rgba8", surface); + glo_readpixels(GL_RGBA, GL_UNSIGNED_BYTE, 4, rgba_stride, + read_width, read_height, flip, rgba_pixels); if (downscale) { rgba_linear = g_malloc(surface->width * surface->height * 4); @@ -2279,17 +1296,11 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, gl_read_buf = pg->scale_buf; } -#ifdef __ANDROID__ - android_glo_readpixels(pg->gl_renderer_state, -#else glo_readpixels( -#endif surface->fmt.gl_format, surface->fmt.gl_type, surface->fmt.bytes_per_pixel, pg->surface_scale_factor * surface->pitch, pg->surface_scale_factor * surface->width, pg->surface_scale_factor * surface->height, flip, gl_read_buf); - android_log_surface_download_errors("surface_download_to_buffer: post-read", - surface); /* FIXME: Replace this with a hw accelerated version */ if (downscale) { @@ -2313,24 +1324,9 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, /* Re-bind original framebuffer target */ cleanup: + glFramebufferTexture2D(GL_FRAMEBUFFER, surface->fmt.gl_attachment, + GL_TEXTURE_2D, 0, 0); bind_current_surface(d); -#ifdef __ANDROID__ - android_log_surface_download_errors( - "surface_download_to_buffer: cleanup_rebind_surface", surface); -#endif -#ifdef __ANDROID__ - if (restore_read_buffer) { - GLenum read_buffer = (GLenum)prev_read_buffer; - if (read_buffer != GL_NONE && - !d->pgraph.gl_renderer_state->color_binding) { - read_buffer = GL_NONE; - } - glReadBuffer(read_buffer); - android_log_surface_download_errors( - "surface_download_to_buffer: cleanup_restore_read_buffer", - surface); - } -#endif } static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force) @@ -2353,9 +1349,6 @@ static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force) memory_region_set_client_dirty(d->vram, surface->vram_addr, surface->pitch * surface->height, DIRTY_MEMORY_NV2A_TEX); - memory_region_set_client_dirty(d->vram, surface->vram_addr, - surface->pitch * surface->height, - DIRTY_MEMORY_NV2A); surface->download_pending = false; surface->draw_dirty = false; @@ -2532,8 +1525,8 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); #ifdef __ANDROID__ { - PGRAPHGLState *r = pg->gl_renderer_state; const uint8_t *upload_buf = gl_read_buf; + uint8_t *upload_tmp = NULL; GLint upload_ifmt; GLenum upload_fmt; GLenum upload_type; @@ -2541,18 +1534,15 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, android_surface_get_storage_format(surface, &upload_ifmt, &upload_fmt, &upload_type); if (android_surface_uses_rgba8_transfer(surface)) { - size_t needed = (size_t)width * height * 4; - if (needed > r->android_conv_buf_size) { - r->android_conv_buf = g_realloc(r->android_conv_buf, needed); - r->android_conv_buf_size = needed; - } + upload_tmp = g_malloc(width * height * 4); android_surface_guest_to_rgba8(surface, gl_read_buf, width, height, width * surface->fmt.bytes_per_pixel, - r->android_conv_buf); - upload_buf = r->android_conv_buf; + upload_tmp); + upload_buf = upload_tmp; } glTexImage2D(GL_TEXTURE_2D, 0, upload_ifmt, width, height, 0, upload_fmt, upload_type, upload_buf); + g_free(upload_tmp); } #else glTexImage2D(GL_TEXTURE_2D, 0, surface->fmt.gl_internal_format, width, @@ -3041,14 +2031,6 @@ void pgraph_gl_init_surfaces(PGRAPHState *pg) pgraph_gl_reload_surface_scale_factor(pg); glGenFramebuffers(1, &r->gl_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer); -#ifdef __ANDROID__ - glGenBuffers(1, &r->gl_download_pbo); - r->gl_download_pbo_size = 0; - r->android_conv_buf = NULL; - r->android_conv_buf_size = 0; - r->android_s2t_conv_buf = NULL; - r->android_s2t_conv_buf_size = 0; -#endif QTAILQ_INIT(&r->surfaces); r->downloads_pending = false; qemu_event_init(&r->downloads_complete, false); @@ -3087,20 +2069,6 @@ void pgraph_gl_finalize_surfaces(PGRAPHState *pg) glDeleteFramebuffers(1, &r->gl_framebuffer); r->gl_framebuffer = 0; -#ifdef __ANDROID__ - if (r->gl_download_pbo) { - glDeleteBuffers(1, &r->gl_download_pbo); - r->gl_download_pbo = 0; - r->gl_download_pbo_size = 0; - } - g_free(r->android_conv_buf); - r->android_conv_buf = NULL; - r->android_conv_buf_size = 0; - g_free(r->android_s2t_conv_buf); - r->android_s2t_conv_buf = NULL; - r->android_s2t_conv_buf_size = 0; -#endif - finalize_render_to_texture(pg); } diff --git a/hw/xbox/nv2a/pgraph/gl/texture.c b/hw/xbox/nv2a/pgraph/gl/texture.c index 12df12b7c2..b71d04f121 100644 --- a/hw/xbox/nv2a/pgraph/gl/texture.c +++ b/hw/xbox/nv2a/pgraph/gl/texture.c @@ -28,46 +28,6 @@ #include "renderer.h" #ifdef __ANDROID__ -#include -#ifdef __aarch64__ -#include -#endif - -static void android_log_gl_errors(const char *ctx) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at %s", err, ctx); - } -} - -static void android_log_texture_stage_errors(int unit, const char *stage, - const TextureShape *shape, - GLenum gl_target) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - if (shape) { - __android_log_print( - ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at pgraph_gl_bind_textures[%d]: %s " - "target=0x%X dim=%u fmt=0x%X levels=%u border=%d cubemap=%d", - err, unit, stage, gl_target, shape->dimensionality, - shape->color_format, shape->levels, shape->border, - shape->cubemap); - } else { - __android_log_print( - ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at pgraph_gl_bind_textures[%d]: %s " - "target=0x%X", - err, unit, stage, gl_target); - } - } -} - static uint8_t android_expand_4_to_8(uint8_t value) { return (value << 4) | value; @@ -234,116 +194,48 @@ static void android_texture_convert_to_rgba8(const TextureShape s, } case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8: - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: - case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: - case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: { - bool preserve_alpha = - (s.color_format != NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8 && - s.color_format != NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8); -#ifdef __aarch64__ - /* NEON BGRA→RGBA shuffle: process 4 pixels (16 bytes) at a time */ - static const uint8_t perm_arr[16] = - {2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15}; - uint8x16_t vperm = vld1q_u8(perm_arr); - static const uint8_t alpha_mask_arr[16] = - {0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF}; - uint8x16_t valpha_mask = preserve_alpha - ? vdupq_n_u8(0) - : vld1q_u8(alpha_mask_arr); - unsigned int remaining = width - x; - const uint8_t *sp = src_row + x * 4; - uint8_t *dp = out; - while (remaining >= 4) { - uint8x16_t v = vqtbl1q_u8(vld1q_u8(sp), vperm); - vst1q_u8(dp, vorrq_u8(v, valpha_mask)); - sp += 16; dp += 16; remaining -= 4; - } - while (remaining-- > 0) { - dp[0] = sp[2]; dp[1] = sp[1]; - dp[2] = sp[0]; dp[3] = preserve_alpha ? sp[3] : 0xFF; - sp += 4; dp += 4; - } - x = width; /* skip remaining scalar iterations */ -#else + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: { const uint8_t *pixel = src_row + x * 4; out[0] = pixel[2]; out[1] = pixel[1]; out[2] = pixel[0]; - out[3] = preserve_alpha ? pixel[3] : 0xFF; -#endif + out[3] = pixel[3]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[2]; + out[1] = pixel[1]; + out[2] = pixel[0]; + out[3] = 0xFF; break; } case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: { -#ifdef __aarch64__ - /* NEON B8G8R8A8→RGBA8: rotate bytes within each pixel */ - static const uint8_t perm_arr[16] = - {1,2,3,0, 5,6,7,4, 9,10,11,8, 13,14,15,12}; - uint8x16_t vperm = vld1q_u8(perm_arr); - unsigned int remaining = width - x; - const uint8_t *sp = src_row + x * 4; - uint8_t *dp = out; - while (remaining >= 4) { - vst1q_u8(dp, vqtbl1q_u8(vld1q_u8(sp), vperm)); - sp += 16; dp += 16; remaining -= 4; - } - while (remaining-- > 0) { - dp[0] = sp[1]; dp[1] = sp[2]; - dp[2] = sp[3]; dp[3] = sp[0]; - sp += 4; dp += 4; - } - x = width; -#else const uint8_t *pixel = src_row + x * 4; out[0] = pixel[1]; out[1] = pixel[2]; out[2] = pixel[3]; out[3] = pixel[0]; -#endif break; } case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: { -#ifdef __aarch64__ - /* A8B8G8R8 is already RGBA8 — bulk memcpy */ - memcpy(out, src_row + x * 4, (width - x) * 4); - x = width; -#else const uint8_t *pixel = src_row + x * 4; out[0] = pixel[0]; out[1] = pixel[1]; out[2] = pixel[2]; out[3] = pixel[3]; -#endif break; } case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: { -#ifdef __aarch64__ - /* NEON R8G8B8A8→RGBA8: full byte reverse per pixel */ - static const uint8_t perm_arr[16] = - {3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12}; - uint8x16_t vperm = vld1q_u8(perm_arr); - unsigned int remaining = width - x; - const uint8_t *sp = src_row + x * 4; - uint8_t *dp = out; - while (remaining >= 4) { - vst1q_u8(dp, vqtbl1q_u8(vld1q_u8(sp), vperm)); - sp += 16; dp += 16; remaining -= 4; - } - while (remaining-- > 0) { - dp[0] = sp[3]; dp[1] = sp[2]; - dp[2] = sp[1]; dp[3] = sp[0]; - sp += 4; dp += 4; - } - x = width; -#else const uint8_t *pixel = src_row + x * 4; out[0] = pixel[3]; out[1] = pixel[2]; out[2] = pixel[1]; out[3] = pixel[0]; -#endif break; } case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_G8B8: @@ -371,8 +263,7 @@ static void android_texture_convert_to_rgba8(const TextureShape s, } } -static void android_prepare_tex_upload(PGRAPHGLState *r, - const TextureShape s, +static void android_prepare_tex_upload(const TextureShape s, const uint8_t *src, unsigned int width, unsigned int height, @@ -382,9 +273,11 @@ static void android_prepare_tex_upload(PGRAPHGLState *r, GLint *ifmt, GLenum *fmt, GLenum *type, - const uint8_t **upload_data) + const uint8_t **upload_data, + uint8_t **upload_tmp) { *upload_data = src; + *upload_tmp = NULL; *ifmt = kelvin_color_format_gl_map[s.color_format].gl_internal_format; *fmt = kelvin_color_format_gl_map[s.color_format].gl_format; *type = kelvin_color_format_gl_map[s.color_format].gl_type; @@ -393,73 +286,17 @@ static void android_prepare_tex_upload(PGRAPHGLState *r, return; } - size_t needed = (size_t)width * height * depth * 4; - if (needed > r->android_tex_conv_buf_size) { - g_free(r->android_tex_conv_buf); - r->android_tex_conv_buf = g_malloc(needed); - r->android_tex_conv_buf_size = needed; - } + *upload_tmp = g_malloc(width * height * depth * 4); android_texture_convert_to_rgba8(s, src, width, height, depth, - row_pitch, slice_pitch, - r->android_tex_conv_buf); - *upload_data = r->android_tex_conv_buf; + row_pitch, slice_pitch, *upload_tmp); + *upload_data = *upload_tmp; *ifmt = GL_RGBA8; *fmt = GL_RGBA; *type = GL_UNSIGNED_BYTE; } #endif -static void pgraph_gl_unbind_texture_targets(void) -{ - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#ifndef __ANDROID__ - glBindTexture(GL_TEXTURE_1D, 0); -#endif - glBindTexture(GL_TEXTURE_2D, 0); - glBindTexture(GL_TEXTURE_3D, 0); -} - -static bool pgraph_gl_texture_range_valid(NV2AState *d, - hwaddr vram_offset, - size_t length) -{ - hwaddr vram_size = memory_region_size(d->vram); - - if (length == 0 || vram_offset >= vram_size) { - return false; - } - - return length <= (vram_size - vram_offset); -} - -static void pgraph_gl_log_invalid_texture_range(NV2AState *d, - int unit, - const char *range_name, - const TextureShape *shape, - hwaddr vram_offset, - size_t length) -{ - hwaddr vram_size = memory_region_size(d->vram); - - NV2A_XPRINTF(true, - "Skipping %s for stage %d: offset=0x%" HWADDR_PRIx - " length=0x%zx vram=0x%" HWADDR_PRIx - " dim=%u fmt=0x%X levels=%u border=%d cubemap=%d\n", - range_name, unit, vram_offset, length, vram_size, - shape->dimensionality, shape->color_format, shape->levels, - shape->border, shape->cubemap); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "Skipping %s for stage %d: offset=0x%" HWADDR_PRIx - " length=0x%zx vram=0x%" HWADDR_PRIx - " dim=%u fmt=0x%X levels=%u border=%d cubemap=%d", - range_name, unit, vram_offset, length, vram_size, - shape->dimensionality, shape->color_format, - shape->levels, shape->border, shape->cubemap); -#endif -} - -static TextureBinding* generate_texture(PGRAPHGLState *r, const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data); +static TextureBinding* generate_texture(const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data); static void texture_binding_destroy(gpointer data); struct pgraph_texture_possibly_dirty_struct { @@ -582,21 +419,22 @@ static void apply_texture_parameters(PGRAPHGLState *r, pgraph_texture_mag_filter_gl_map[mag_filter]); binding->mag_filter = mag_filter; } +#ifndef __ANDROID__ if (lod_bias != binding->lod_bias) { binding->lod_bias = lod_bias; - if (r->supported_extensions.texture_lod_bias) { - glTexParameterf(binding->gl_target, NV2A_GL_TEXTURE_LOD_BIAS, - pgraph_convert_lod_bias_to_float(lod_bias)); - } + glTexParameterf(binding->gl_target, GL_TEXTURE_LOD_BIAS, + pgraph_convert_lod_bias_to_float(lod_bias)); } +#else + binding->lod_bias = lod_bias; +#endif /* Texture wrapping */ assert(addru < ARRAY_SIZE(pgraph_texture_addr_gl_map)); if (addru != binding->addru) { GLenum wrap_s = pgraph_texture_addr_gl_map[addru]; #ifdef __ANDROID__ - if (!r->supported_extensions.texture_border_clamp && - wrap_s == NV2A_GL_CLAMP_TO_BORDER) { + if (wrap_s == GL_CLAMP_TO_BORDER) { wrap_s = GL_CLAMP_TO_EDGE; } #endif @@ -610,8 +448,7 @@ static void apply_texture_parameters(PGRAPHGLState *r, assert(addrv < ARRAY_SIZE(pgraph_texture_addr_gl_map)); GLenum wrap_t = pgraph_texture_addr_gl_map[addrv]; #ifdef __ANDROID__ - if (!r->supported_extensions.texture_border_clamp && - wrap_t == NV2A_GL_CLAMP_TO_BORDER) { + if (wrap_t == GL_CLAMP_TO_BORDER) { wrap_t = GL_CLAMP_TO_EDGE; } #endif @@ -626,8 +463,7 @@ static void apply_texture_parameters(PGRAPHGLState *r, assert(addrp < ARRAY_SIZE(pgraph_texture_addr_gl_map)); GLenum wrap_r = pgraph_texture_addr_gl_map[addrp]; #ifdef __ANDROID__ - if (!r->supported_extensions.texture_border_clamp && - wrap_r == NV2A_GL_CLAMP_TO_BORDER) { + if (wrap_r == GL_CLAMP_TO_BORDER) { wrap_r = GL_CLAMP_TO_EDGE; } #endif @@ -649,19 +485,15 @@ static void apply_texture_parameters(PGRAPHGLState *r, clamped_anisotropy); } - if (!is_bordered && needs_border_color) { #ifdef __ANDROID__ - if (!r->supported_extensions.texture_border_clamp) { - needs_border_color = false; - } + needs_border_color = false; #endif - } if (!is_bordered && needs_border_color) { if (!binding->border_color_set || binding->border_color != border_color) { /* FIXME: Color channels might be wrong order */ GLfloat gl_border_color[4]; pgraph_argb_pack32_to_rgba_float(border_color, gl_border_color); - glTexParameterfv(binding->gl_target, NV2A_GL_TEXTURE_BORDER_COLOR, + glTexParameterfv(binding->gl_target, GL_TEXTURE_BORDER_COLOR, gl_border_color); binding->border_color_set = true; @@ -683,14 +515,13 @@ void pgraph_gl_bind_textures(NV2AState *d) /* FIXME: What happens if texture is disabled but stage is active? */ glActiveTexture(GL_TEXTURE0 + i); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "after_active_texture", NULL, 0); -#endif if (!enabled) { - pgraph_gl_unbind_texture_targets(); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "disabled_unbind", NULL, 0); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#ifndef __ANDROID__ + glBindTexture(GL_TEXTURE_1D, 0); #endif + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_3D, 0); continue; } @@ -701,54 +532,25 @@ void pgraph_gl_bind_textures(NV2AState *d) 1 << (GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_TEXCTL0_0 + i*4), NV_PGRAPH_TEXCTL0_0_MAX_ANISOTROPY)); + /* Check for unsupported features */ + if (filter & NV_PGRAPH_TEXFILTER0_ASIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_ASIGNED"); + if (filter & NV_PGRAPH_TEXFILTER0_RSIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_RSIGNED"); + if (filter & NV_PGRAPH_TEXFILTER0_GSIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_GSIGNED"); + if (filter & NV_PGRAPH_TEXFILTER0_BSIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_BSIGNED"); + TextureShape state = pgraph_get_texture_shape(pg, i); - hwaddr texture_vram_offset, palette_vram_offset = 0; - size_t length, palette_length = 0; - bool is_indexed = (state.color_format == - NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8); + hwaddr texture_vram_offset, palette_vram_offset; + size_t length, palette_length; length = pgraph_get_texture_length(pg, &state); texture_vram_offset = pgraph_get_texture_phys_addr(pg, i); - if (is_indexed) { - palette_vram_offset = pgraph_get_texture_palette_phys_addr_length( - pg, i, &palette_length); - } - - if (!pgraph_gl_texture_range_valid(d, texture_vram_offset, length)) { - pgraph_gl_log_invalid_texture_range(d, i, "texture", &state, - texture_vram_offset, length); - pgraph_gl_unbind_texture_targets(); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "invalid_texture_range", - &state, 0); -#endif - if (r->texture_binding[i]) { - texture_binding_destroy(r->texture_binding[i]); - r->texture_binding[i] = NULL; - } - pg->texture_dirty[i] = false; - continue; - } - - if (is_indexed && - !pgraph_gl_texture_range_valid(d, palette_vram_offset, - palette_length)) { - pgraph_gl_log_invalid_texture_range(d, i, "palette", &state, - palette_vram_offset, - palette_length); - pgraph_gl_unbind_texture_targets(); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "invalid_palette_range", - &state, 0); -#endif - if (r->texture_binding[i]) { - texture_binding_destroy(r->texture_binding[i]); - r->texture_binding[i] = NULL; - } - pg->texture_dirty[i] = false; - continue; - } + palette_vram_offset = pgraph_get_texture_palette_phys_addr_length(pg, i, &palette_length); + assert((texture_vram_offset + length) < memory_region_size(d->vram)); + assert((palette_vram_offset + palette_length) + < memory_region_size(d->vram)); + bool is_indexed = (state.color_format == + NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8); bool possibly_dirty = false; bool possibly_dirty_checked = false; @@ -772,11 +574,6 @@ void pgraph_gl_bind_textures(NV2AState *d) if (reusable) { glBindTexture(r->texture_binding[i]->gl_target, r->texture_binding[i]->gl_texture); -#ifdef __ANDROID__ - android_log_texture_stage_errors( - i, "reuse_bind_existing", &state, - r->texture_binding[i]->gl_target); -#endif apply_texture_parameters(r, r->texture_binding[i], &kelvin_color_format_info_map[state.color_format], @@ -786,11 +583,6 @@ void pgraph_gl_bind_textures(NV2AState *d) state.border, border_color, max_anisotropy); -#ifdef __ANDROID__ - android_log_texture_stage_errors( - i, "reuse_apply_texture_parameters", &state, - r->texture_binding[i]->gl_target); -#endif continue; } } @@ -805,10 +597,6 @@ void pgraph_gl_bind_textures(NV2AState *d) if (surf_to_tex && surface->upload_pending) { pgraph_gl_upload_surface_data(d, surface, false); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "surface_upload_pending", - &state, GL_TEXTURE_2D); -#endif } } @@ -823,10 +611,6 @@ void pgraph_gl_bind_textures(NV2AState *d) || texture_vram_offset >= surf_vram_end); if (overlapping) { pgraph_gl_surface_download_if_dirty(d, surface); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "download_overlap", - &state, GL_TEXTURE_2D); -#endif } } } @@ -880,21 +664,13 @@ void pgraph_gl_bind_textures(NV2AState *d) if (key_out->binding == NULL) { // Must create the texture - key_out->binding = generate_texture(r, state, texture_data, palette_data); + key_out->binding = generate_texture(state, texture_data, palette_data); key_out->binding->data_hash = tex_data_hash; key_out->binding->scale = 1; -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "generate_texture", &state, - key_out->binding->gl_target); -#endif } else { // Saved an upload! Reuse existing texture in graphics memory. glBindTexture(key_out->binding->gl_target, key_out->binding->gl_texture); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "reuse_cached_binding", &state, - key_out->binding->gl_target); -#endif } key_out->possibly_dirty = false; @@ -908,10 +684,6 @@ void pgraph_gl_bind_textures(NV2AState *d) pgraph_gl_render_surface_to_texture(d, surface, binding, &state, i); binding->draw_time = surface->draw_time; binding->scale = pg->surface_scale_factor; -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "render_surface_to_texture", - &state, binding->gl_target); -#endif } apply_texture_parameters(r, @@ -923,29 +695,16 @@ void pgraph_gl_bind_textures(NV2AState *d) state.border, border_color, max_anisotropy); -#ifdef __ANDROID__ - android_log_texture_stage_errors(i, "apply_texture_parameters", &state, - binding->gl_target); -#endif if (r->texture_binding[i]) { if (r->texture_binding[i]->gl_target != binding->gl_target) { glBindTexture(r->texture_binding[i]->gl_target, 0); -#ifdef __ANDROID__ - android_log_texture_stage_errors( - i, "unbind_old_target", &state, - r->texture_binding[i]->gl_target); -#endif } texture_binding_destroy(r->texture_binding[i]); } r->texture_binding[i] = binding; pg->texture_dirty[i] = false; } - -#ifdef __ANDROID__ - android_log_gl_errors("pgraph_gl_bind_textures"); -#endif NV2A_GL_DGROUP_END(); } @@ -964,13 +723,11 @@ gl_internal_format_to_s3tc_enum(GLint gl_internal_format) } } -static void upload_gl_texture(PGRAPHGLState *r, - GLenum gl_target, +static void upload_gl_texture(GLenum gl_target, const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data) { - (void)r; /* used only in Android-specific paths */ ColorFormatInfo f = kelvin_color_format_gl_map[s.color_format]; nv2a_profile_inc_counter(NV2A_PROF_TEX_UPLOAD); #ifdef __ANDROID__ @@ -1007,6 +764,7 @@ static void upload_gl_texture(PGRAPHGLState *r, { const uint8_t *pixel_data = converted ? converted : texture_data; const uint8_t *upload_data = pixel_data; + uint8_t *upload_tmp = NULL; unsigned int source_bpp = #ifdef __ANDROID__ android_texture_source_bpp(s, f.bytes_per_pixel, @@ -1020,11 +778,11 @@ static void upload_gl_texture(PGRAPHGLState *r, GLint tex_ifmt = f.gl_internal_format; GLenum tex_type = f.gl_type; #ifdef __ANDROID__ - android_prepare_tex_upload(r, s, pixel_data, adjusted_width, + android_prepare_tex_upload(s, pixel_data, adjusted_width, adjusted_height, 1, row_pitch, row_pitch * adjusted_height, &tex_ifmt, &tex_fmt, &tex_type, - &upload_data); + &upload_data, &upload_tmp); #endif glPixelStorei(GL_UNPACK_ROW_LENGTH, upload_data == texture_data ? @@ -1034,6 +792,9 @@ static void upload_gl_texture(PGRAPHGLState *r, tex_fmt, tex_type, upload_data); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +#ifdef __ANDROID__ + g_free(upload_tmp); +#endif } if (converted) { @@ -1068,10 +829,8 @@ static void upload_gl_texture(PGRAPHGLState *r, texture_data, width, height); unsigned int tex_width = width; unsigned int tex_height = height; - bool need_cubemap_border_strip = - s.cubemap && adjusted_width != s.width; - if (need_cubemap_border_strip) { + if (s.cubemap && adjusted_width != s.width) { // FIXME: Consider preserving the border. // There does not seem to be a way to reference the border // texels in a cubemap, so they are discarded. @@ -1089,7 +848,7 @@ static void upload_gl_texture(PGRAPHGLState *r, glTexImage2D(gl_target, level, GL_RGBA8, tex_width, tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, converted); g_free(converted); - if (need_cubemap_border_strip) { + if (s.cubemap && adjusted_width != s.width) { glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); if (physical_width == width) { @@ -1129,15 +888,16 @@ static void upload_gl_texture(PGRAPHGLState *r, { const uint8_t *upload_data = pixel_data; + uint8_t *upload_tmp = NULL; GLenum tex_fmt = f.gl_format; GLint tex_ifmt = f.gl_internal_format; GLenum tex_type = f.gl_type; #ifdef __ANDROID__ - android_prepare_tex_upload(r, s, pixel_data, tex_width, + android_prepare_tex_upload(s, pixel_data, tex_width, tex_height, 1, row_pitch, row_pitch * tex_height, &tex_ifmt, &tex_fmt, &tex_type, - &upload_data); + &upload_data, &upload_tmp); #endif glPixelStorei(GL_UNPACK_ROW_LENGTH, upload_data == pixel_data && @@ -1147,6 +907,9 @@ static void upload_gl_texture(PGRAPHGLState *r, tex_height, 0, tex_fmt, tex_type, upload_data); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +#ifdef __ANDROID__ + g_free(upload_tmp); +#endif } if (converted) { g_free(converted); @@ -1219,6 +982,7 @@ static void upload_gl_texture(PGRAPHGLState *r, { const uint8_t *pixel_data = converted ? converted : unswizzled; const uint8_t *upload_data = pixel_data; + uint8_t *upload_tmp = NULL; unsigned int source_bpp = #ifdef __ANDROID__ android_texture_source_bpp(s, f.bytes_per_pixel, @@ -1232,16 +996,19 @@ static void upload_gl_texture(PGRAPHGLState *r, GLint tex_ifmt = f.gl_internal_format; GLenum tex_type = f.gl_type; #ifdef __ANDROID__ - android_prepare_tex_upload(r, s, pixel_data, width, height, + android_prepare_tex_upload(s, pixel_data, width, height, depth, upload_row_pitch, upload_slice_pitch, &tex_ifmt, &tex_fmt, &tex_type, - &upload_data); + &upload_data, &upload_tmp); #endif glTexImage3D(gl_target, level, tex_ifmt, width, height, depth, 0, tex_fmt, tex_type, upload_data); +#ifdef __ANDROID__ + g_free(upload_tmp); +#endif } if (converted) { @@ -1267,8 +1034,7 @@ static void upload_gl_texture(PGRAPHGLState *r, #endif } -static TextureBinding* generate_texture(PGRAPHGLState *r, - const TextureShape s, +static TextureBinding* generate_texture(const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data) { @@ -1345,20 +1111,20 @@ static TextureBinding* generate_texture(PGRAPHGLState *r, length = (length + NV2A_CUBEMAP_FACE_ALIGNMENT - 1) & ~(NV2A_CUBEMAP_FACE_ALIGNMENT - 1); - upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_POSITIVE_X, + upload_gl_texture(GL_TEXTURE_CUBE_MAP_POSITIVE_X, s, texture_data + 0 * length, palette_data); - upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + upload_gl_texture(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, s, texture_data + 1 * length, palette_data); - upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + upload_gl_texture(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, s, texture_data + 2 * length, palette_data); - upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + upload_gl_texture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, s, texture_data + 3 * length, palette_data); - upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + upload_gl_texture(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, s, texture_data + 4 * length, palette_data); - upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, + upload_gl_texture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, s, texture_data + 5 * length, palette_data); } else { - upload_gl_texture(r, gl_target, s, texture_data, palette_data); + upload_gl_texture(gl_target, s, texture_data, palette_data); } /* Linear textures don't support mipmapping */ @@ -1377,21 +1143,8 @@ static TextureBinding* generate_texture(PGRAPHGLState *r, } #endif if (apply_swizzle) { -#ifdef __ANDROID__ - /* GLES exposes per-channel texture swizzles, not the desktop RGBA - * vector pname. */ - glTexParameteri(gl_target, GL_TEXTURE_SWIZZLE_R, - f.gl_swizzle_mask[0]); - glTexParameteri(gl_target, GL_TEXTURE_SWIZZLE_G, - f.gl_swizzle_mask[1]); - glTexParameteri(gl_target, GL_TEXTURE_SWIZZLE_B, - f.gl_swizzle_mask[2]); - glTexParameteri(gl_target, GL_TEXTURE_SWIZZLE_A, - f.gl_swizzle_mask[3]); -#else glTexParameteriv(gl_target, GL_TEXTURE_SWIZZLE_RGBA, (const GLint *)f.gl_swizzle_mask); -#endif } TextureBinding* ret = (TextureBinding *)g_malloc(sizeof(TextureBinding)); @@ -1454,7 +1207,7 @@ void pgraph_gl_init_textures(NV2AState *d) PGRAPHGLState *r = pg->gl_renderer_state; const size_t texture_cache_size = 512; - lru_init(&r->texture_cache, 1024); + lru_init(&r->texture_cache, 1u << 16); r->texture_cache_entries = malloc(texture_cache_size * sizeof(TextureLruNode)); assert(r->texture_cache_entries != NULL); for (int i = 0; i < texture_cache_size; i++) { @@ -1475,13 +1228,7 @@ void pgraph_gl_finalize_textures(PGRAPHState *pg) } lru_flush(&r->texture_cache); - lru_destroy(&r->texture_cache); free(r->texture_cache_entries); r->texture_cache_entries = NULL; -#ifdef __ANDROID__ - g_free(r->android_tex_conv_buf); - r->android_tex_conv_buf = NULL; - r->android_tex_conv_buf_size = 0; -#endif } diff --git a/hw/xbox/nv2a/pgraph/gl/vertex.c b/hw/xbox/nv2a/pgraph/gl/vertex.c index 4d1abe652b..30a2df3bb9 100644 --- a/hw/xbox/nv2a/pgraph/gl/vertex.c +++ b/hw/xbox/nv2a/pgraph/gl/vertex.c @@ -24,25 +24,6 @@ #include "debug.h" #include "renderer.h" -#ifdef __ANDROID__ -#include - -static void android_log_gl_errors(const char *ctx) -{ - GLenum err; - - while ((err = glGetError()) != GL_NO_ERROR) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "GL error 0x%X at %s", err, ctx); - } -} -#else -static inline void android_log_gl_errors(const char *ctx) -{ - (void)ctx; -} -#endif - static void update_memory_buffer(NV2AState *d, hwaddr addr, hwaddr size, bool quick) { @@ -224,7 +205,6 @@ void pgraph_gl_bind_vertex_attributes(NV2AState *d, unsigned int min_element, pgraph_update_inline_value(attr, last_entry); } - android_log_gl_errors("pgraph_gl_bind_vertex_attributes"); NV2A_GL_DGROUP_END(); } @@ -285,7 +265,7 @@ void pgraph_gl_init_buffers(NV2AState *d) PGRAPHState *pg = &d->pgraph; PGRAPHGLState *r = pg->gl_renderer_state; - lru_init(&r->element_cache, 1 << 16); + lru_init(&r->element_cache, 1u << 16); r->element_cache_entries = g_malloc_n(element_cache_size, sizeof(VertexLruNode)); assert(r->element_cache_entries != NULL); GLuint element_cache_buffers[element_cache_size]; @@ -328,7 +308,6 @@ void pgraph_gl_finalize_buffers(PGRAPHState *pg) } glDeleteBuffers(element_cache_size, element_cache_buffers); lru_flush(&r->element_cache); - lru_destroy(&r->element_cache); g_free(r->element_cache_entries); r->element_cache_entries = NULL; diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.c b/hw/xbox/nv2a/pgraph/glsl/geom.c index c70565d9f8..64e957b654 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.c +++ b/hw/xbox/nv2a/pgraph/glsl/geom.c @@ -134,11 +134,13 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) pgraph_glsl_get_vtx_header(output, opts.vulkan, state->smooth_shading, false, false, false); + const char *point_size_expr = + opts.gles ? "v_vtxPointSize[index]" : "gl_in[index].gl_PointSize"; mstring_append(output, "void emit_vertex(int index, mat4 pz) {\n" " gl_Position = gl_in[index].gl_Position;\n"); - if (need_point_size && !opts.gles) { - mstring_append(output, " gl_PointSize = gl_in[index].gl_PointSize;\n"); + if (need_point_size) { + mstring_append_fmt(output, " gl_PointSize = %s;\n", point_size_expr); } mstring_append_fmt( output, diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index 3c3a7f6ef6..ec18562849 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -29,7 +29,6 @@ #include "qemu/osdep.h" #include "hw/xbox/nv2a/debug.h" #include "hw/xbox/nv2a/pgraph/pgraph.h" -#include "hw/xbox/nv2a/pgraph/texture.h" #include "psh.h" DEF_UNIFORM_INFO_ARR(PshUniform, PSH_UNIFORM_DECL_X) @@ -180,24 +179,13 @@ void pgraph_glsl_set_psh_state(PGRAPHState *pg, PshState *state) } } - /* Keep track of whether texture data has been loaded as signed - * normalized integers or not. This dictates whether or not we will need - * to re-map in fragment shader for certain texture modes (e.g. - * bumpenvmap). - * - * FIXME: When signed texture data is loaded as unsigned and remapped in - * fragment shader, there may be interpolation artifacts. Fix this to - * support signed textures more appropriately. - */ -#if 0 // FIXME - psh->snorm_tex[i] = (f.gl_internal_format == GL_RGB8_SNORM) - || (f.gl_internal_format == GL_RG8_SNORM); -#endif + /* Keep track of textures that are uploaded as signed normalized data. + * Those must not be remapped a second time in the fragment shader. */ + state->snorm_tex[i] = + color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R6G5B5; state->shadow_map[i] = f.depth; uint32_t filter = pgraph_reg_r(pg, NV_PGRAPH_TEXFILTER0 + i * 4); - state->tex_signed[i] = - pgraph_get_texture_signed_component_mask_from_filter(filter); unsigned int min_filter = GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIN); enum ConvolutionFilter kernel = CONVOLUTION_FILTER_DISABLED; /* FIXME: We do not distinguish between min and mag when @@ -805,31 +793,6 @@ static void define_colorkey_comparator(MString *preflight) // clang-format on } -static bool texture_channels_are_signed(const struct PixelShader *ps, int tex, - uint8_t mask) -{ - return (ps->state->tex_signed[tex] & mask) == mask; -} - -static void apply_signed_texture_remap(const struct PixelShader *ps, - MString *vars, int tex) -{ - uint8_t mask = ps->state->tex_signed[tex]; - - if (mask & PGRAPH_TEXTURE_SIGNED_R) { - mstring_append_fmt(vars, "t%d.r = sign3(t%d.r);\n", tex, tex); - } - if (mask & PGRAPH_TEXTURE_SIGNED_G) { - mstring_append_fmt(vars, "t%d.g = sign3(t%d.g);\n", tex, tex); - } - if (mask & PGRAPH_TEXTURE_SIGNED_B) { - mstring_append_fmt(vars, "t%d.b = sign3(t%d.b);\n", tex, tex); - } - if (mask & PGRAPH_TEXTURE_SIGNED_A) { - mstring_append_fmt(vars, "t%d.a = sign3(t%d.a);\n", tex, tex); - } -} - static MString* psh_convert(struct PixelShader *ps) { MString *preflight = mstring_new(); @@ -837,19 +800,11 @@ static MString* psh_convert(struct PixelShader *ps) ps->state->smooth_shading, true, false, false); if (ps->opts.vulkan) { - if (ps->opts.ubo_set > 0) { - mstring_append_fmt( - preflight, - "layout(location = 0) out vec4 fragColor;\n" - "layout(set = %d, binding = %d, std140) uniform PshUniforms {\n", - ps->opts.ubo_set, ps->opts.ubo_binding); - } else { - mstring_append_fmt( - preflight, - "layout(location = 0) out vec4 fragColor;\n" - "layout(binding = %d, std140) uniform PshUniforms {\n", - ps->opts.ubo_binding); - } + mstring_append_fmt( + preflight, + "layout(location = 0) out vec4 fragColor;\n" + "layout(binding = %d, std140) uniform PshUniforms {\n", + ps->opts.ubo_binding); } else { mstring_append_fmt(preflight, "layout(location = 0) out vec4 fragColor;\n"); @@ -878,18 +833,6 @@ static MString* psh_convert(struct PixelShader *ps) mstring_append(preflight, "};\n"); } - if (ps->opts.bindless) { - mstring_append(preflight, - "layout(set = 0, binding = 0) uniform sampler2D texArray2D[1024];\n" - "layout(set = 0, binding = 1) uniform sampler3D texArray3D[1024];\n" - "layout(set = 0, binding = 2) uniform samplerCube texArrayCube[1024];\n"); - mstring_append_fmt(preflight, - "layout(push_constant) uniform TexPushData {\n" - " layout(offset = %d) uint texIdx[4];\n" - "};\n", - ps->opts.tex_push_offset); - } - const char *dotmap_funcs[] = { "dotmap_zero_to_one", "dotmap_minus1_to_1_d3d", @@ -1221,10 +1164,7 @@ static MString* psh_convert(struct PixelShader *ps) case PS_TEXTUREMODES_BUMPENVMAP: assert(i >= 1); - if (ps->state->snorm_tex[ps->input_tex[i]] || - texture_channels_are_signed( - ps, ps->input_tex[i], - PGRAPH_TEXTURE_SIGNED_G | PGRAPH_TEXTURE_SIGNED_B)) { + if (ps->state->snorm_tex[ps->input_tex[i]]) { /* Input color channels already signed (FIXME: May not always want signed textures in this case) */ mstring_append_fmt(vars, "vec2 dsdt%d = t%d.bg;\n", i, ps->input_tex[i]); @@ -1250,28 +1190,15 @@ static MString* psh_convert(struct PixelShader *ps) case PS_TEXTUREMODES_BUMPENVMAP_LUM: assert(i >= 1); - { - bool bg_signed = - ps->state->snorm_tex[ps->input_tex[i]] || - texture_channels_are_signed( - ps, ps->input_tex[i], - PGRAPH_TEXTURE_SIGNED_G | PGRAPH_TEXTURE_SIGNED_B); - bool r_signed = - ps->state->snorm_tex[ps->input_tex[i]] || - texture_channels_are_signed( - ps, ps->input_tex[i], PGRAPH_TEXTURE_SIGNED_R); - g_autofree gchar *b_expr = g_strdup_printf( - bg_signed ? "t%d.b" : "sign3(t%d.b)", ps->input_tex[i]); - g_autofree gchar *g_expr = g_strdup_printf( - bg_signed ? "t%d.g" : "sign3(t%d.g)", ps->input_tex[i]); - g_autofree gchar *r_expr = g_strdup_printf( - r_signed ? "sign3_to_0_to_1(t%d.r)" : "t%d.r", - ps->input_tex[i]); - - mstring_append_fmt(vars, - "vec3 dsdtl%d = vec3(%s, %s, %s);\n", i, - b_expr, g_expr, r_expr); - } + if (ps->state->snorm_tex[ps->input_tex[i]]) { + /* Input color channels already signed (FIXME: May not always want signed textures in this case) */ + mstring_append_fmt(vars, "vec3 dsdtl%d = vec3(t%d.bg, sign3_to_0_to_1(t%d.r));\n", + i, ps->input_tex[i], ps->input_tex[i]); + } else { + /* Convert to signed (FIXME: loss of accuracy due to filtering/interpolation) */ + mstring_append_fmt(vars, "vec3 dsdtl%d = vec3(sign3(t%d.b), sign3(t%d.g), t%d.r);\n", + i, ps->input_tex[i], ps->input_tex[i], ps->input_tex[i]); + } mstring_append_fmt(vars, "dsdtl%d.st = bumpMat[%d] * dsdtl%d.st;\n", i, i, i); @@ -1422,26 +1349,10 @@ static MString* psh_convert(struct PixelShader *ps) } if (sampler_type != NULL) { - if (ps->opts.bindless) { - const char *array_name; - if (strcmp(sampler_type, "sampler3D") == 0) { - array_name = "texArray3D"; - } else if (strcmp(sampler_type, "samplerCube") == 0) { - array_name = "texArrayCube"; - } else { - array_name = "texArray2D"; - } - mstring_append_fmt(preflight, - "#define texSamp%d %s[texIdx[%d]]\n", - i, array_name, i); - } else { - if (ps->opts.vulkan) { - mstring_append_fmt(preflight, "layout(binding = %d) ", - ps->opts.tex_binding + i); - } - mstring_append_fmt(preflight, "uniform %s texSamp%d;\n", - sampler_type, i); + if (ps->opts.vulkan) { + mstring_append_fmt(preflight, "layout(binding = %d) ", ps->opts.tex_binding + i); } + mstring_append_fmt(preflight, "uniform %s texSamp%d;\n", sampler_type, i); /* As this means a texture fetch does happen, do alphakill */ if (ps->state->alphakill[i]) { @@ -1483,10 +1394,6 @@ static MString* psh_convert(struct PixelShader *ps) mstring_append(vars, "}\n"); } - if (!ps->state->shadow_map[i] && !ps->state->tex_x8y24[i]) { - apply_signed_texture_remap(ps, vars, i); - } - if (ps->state->rect_tex[i]) { mstring_append_fmt(preflight, "vec2 norm%d(vec2 coord) {\n" diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.h b/hw/xbox/nv2a/pgraph/glsl/psh.h index bef84ec287..ea3045ab32 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.h +++ b/hw/xbox/nv2a/pgraph/glsl/psh.h @@ -47,7 +47,6 @@ typedef struct PshState { bool point_sprite; bool rect_tex[4]; bool snorm_tex[4]; - uint8_t tex_signed[4]; bool compare_mode[4][4]; bool alphakill[4]; int colorkey_mode[4]; @@ -100,10 +99,7 @@ typedef struct GenPshGlslOptions { bool gles; int gles_version; int ubo_binding; - int ubo_set; int tex_binding; - bool bindless; - int tex_push_offset; } GenPshGlslOptions; MString *pgraph_glsl_gen_psh(const PshState *state, GenPshGlslOptions opts); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 0e010369c7..5e5c42ab11 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -262,6 +262,8 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz static char alpha_source_specular[] = "specular.a"; static char alpha_source_material[] = "material_alpha"; const char *alpha_source = alpha_source_diffuse; + const char *ambient_source_rgb = "diffuse.rgb"; + const char *emission_source_rgb = "diffuse.rgb"; if (state->fixed_function.diffuse_src == MATERIAL_COLOR_SRC_MATERIAL) { alpha_source = alpha_source_material; } else if (state->fixed_function.diffuse_src == MATERIAL_COLOR_SRC_SPECULAR) { @@ -269,21 +271,23 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } if (state->fixed_function.ambient_src == MATERIAL_COLOR_SRC_MATERIAL) { - mstring_append_fmt(body, "oD0 = vec4(sceneAmbientColor, %s);\n", alpha_source); + ambient_source_rgb = "materialEmissionColor.rgb"; } else if (state->fixed_function.ambient_src == MATERIAL_COLOR_SRC_DIFFUSE) { - mstring_append_fmt(body, "oD0 = vec4(diffuse.rgb, %s);\n", alpha_source); + ambient_source_rgb = "diffuse.rgb"; } else if (state->fixed_function.ambient_src == MATERIAL_COLOR_SRC_SPECULAR) { - mstring_append_fmt(body, "oD0 = vec4(specular.rgb, %s);\n", alpha_source); + ambient_source_rgb = "specular.rgb"; } - mstring_append(body, "oD0.rgb *= materialEmissionColor.rgb;\n"); if (state->fixed_function.emission_src == MATERIAL_COLOR_SRC_MATERIAL) { - mstring_append(body, "oD0.rgb += sceneAmbientColor;\n"); + emission_source_rgb = "materialEmissionColor.rgb"; } else if (state->fixed_function.emission_src == MATERIAL_COLOR_SRC_DIFFUSE) { - mstring_append(body, "oD0.rgb += diffuse.rgb;\n"); + emission_source_rgb = "diffuse.rgb"; } else if (state->fixed_function.emission_src == MATERIAL_COLOR_SRC_SPECULAR) { - mstring_append(body, "oD0.rgb += specular.rgb;\n"); + emission_source_rgb = "specular.rgb"; } + mstring_append_fmt(body, "oD0 = vec4(sceneAmbientColor * %s, %s);\n", + ambient_source_rgb, alpha_source); + mstring_append_fmt(body, "oD0.rgb += %s;\n", emission_source_rgb); mstring_append(body, "oD1 = vec4(0.0, 0.0, 0.0, specular.a);\n"); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index efb2dd8ff0..e496f83e88 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -209,7 +209,6 @@ static bool ilu_force_scalar[] = { false, }; -#define OUTPUT_REG_POS 0 #define OUTPUT_REG_FOG 5 static const char* out_reg_name[] = { @@ -305,6 +304,7 @@ static MString *decode_opcode_input(const uint32_t *shader_token, MString *ret_str = mstring_new(); + if (vsh_get_field(shader_token, neg_field) > 0) { mstring_append_fmt(ret_str, "-"); } @@ -354,7 +354,6 @@ static MString *decode_opcode(const uint32_t *shader_token, MString *ret = mstring_new(); int reg_num = vsh_get_field(shader_token, FLD_OUT_R); bool use_temp_var = false; - bool use_temp_opos = false; /* Test for paired opcodes (in other words : Are both <> NOP?) */ if (out_mux == OMUX_MAC @@ -370,8 +369,6 @@ static MString *decode_opcode(const uint32_t *shader_token, reg_num = 1; } - const char *write_mask_str = NULL; - /* See if we must add a muxed opcode too: */ if (vsh_get_field(shader_token, FLD_OUT_MUX) == out_mux /* Only if it's not masked away: */ @@ -389,18 +386,12 @@ static MString *decode_opcode(const uint32_t *shader_token, shader_token, FLD_OUT_ADDRESS))); } else { int out_reg = vsh_get_field(shader_token, FLD_OUT_ADDRESS) & 0xF; - if (out_reg == OUTPUT_REG_POS && (use_temp_var || mask)) { - use_temp_opos = true; - mstring_append(ret, "_temp_r12"); - } else { - mstring_append(ret, out_reg_name[out_reg]); - } + mstring_append(ret,out_reg_name[out_reg]); write_fog_register = out_reg == OUTPUT_REG_FOG; } int write_mask = vsh_get_field(shader_token, FLD_OUT_O_MASK); - write_mask_str = write_fog_register ? fog_mask_str[write_mask] - : mask_str[write_mask]; + const char *write_mask_str = write_fog_register ? fog_mask_str[write_mask] : mask_str[write_mask]; mstring_append(ret, write_mask_str); mstring_append(ret, inputs); mstring_append(ret, ");\n"); @@ -437,16 +428,6 @@ static MString *decode_opcode(const uint32_t *shader_token, } } - if (use_temp_opos) { - if (!*suffix) { - *suffix = mstring_new(); - } - assert(write_mask_str && "Temp oPos flagged without a mask"); - const char *write_mask_components = write_mask_str + 1; - mstring_append_fmt(*suffix, " oPos.%s = _temp_r12.%s;\n", - write_mask_components, write_mask_components); - } - return ret; } @@ -470,7 +451,7 @@ static MString *decode_token(const uint32_t *shader_token) (vsh_get_field(shader_token, FLD_C_R_HIGH) << 2) | vsh_get_field(shader_token, FLD_C_R_LOW)); - MString *suffix = NULL; + MString *mac_suffix = NULL; if (mac != MAC_NOP) { MString *inputs_mac = mstring_new(); if (mac_opcode_params[mac].A) { @@ -504,7 +485,7 @@ static MString *decode_token(const uint32_t *shader_token) vsh_get_field(shader_token, FLD_OUT_MAC_MASK), mac_opcode[mac], mstring_get_str(inputs_mac), - &suffix); + &mac_suffix); mstring_unref(inputs_mac); } else { ret = mstring_new(); @@ -521,7 +502,7 @@ static MString *decode_token(const uint32_t *shader_token) vsh_get_field(shader_token, FLD_OUT_ILU_MASK), ilu_opcode[ilu], mstring_get_str(inputs_c), - &suffix); + NULL); mstring_append(ret, mstring_get_str(ilu_op)); @@ -531,9 +512,9 @@ static MString *decode_token(const uint32_t *shader_token) mstring_unref(input_c); - if (suffix) { - mstring_append(ret, mstring_get_str(suffix)); - mstring_unref(suffix); + if (mac_suffix) { + mstring_append(ret, mstring_get_str(mac_suffix)); + mstring_unref(mac_suffix); } return ret; @@ -562,11 +543,6 @@ static const char* vsh_header = "vec4 _temp_vec;\n" "int _temp_addr;\n" - /* Used to emulate concurrency of multi-output / paired instructions that - * write to oPos and read from R12 - */ - "vec4 _temp_r12;\n" - /* See: * http://msdn.microsoft.com/en-us/library/windows/desktop/bb174703%28v=vs.85%29.aspx * https://www.opengl.org/registry/specs/NV/vertex_program1_1.txt diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index ab041399bb..467a3873f9 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -451,37 +451,21 @@ MString *pgraph_glsl_gen_vsh(const VshState *state, GenVshGlslOptions opts) opts.gles_version); if (opts.vulkan) { + // FIXME: Optimize uniforms if (num_uniform_attrs > 0 && opts.use_push_constants_for_uniform_attrs) { - if (opts.vertex_push_offset > 0) { - mstring_append_fmt(output, - "layout(push_constant) uniform PushConstants {\n" - " layout(offset = %d) vec4 inlineValue[%d];\n" - "};\n\n", - opts.vertex_push_offset, num_uniform_attrs); - } else { - mstring_append_fmt(output, - "layout(push_constant) uniform PushConstants {\n" - " vec4 inlineValue[%d];\n" - "};\n\n", - num_uniform_attrs); - } - } - if (opts.ubo_set > 0) { - mstring_append_fmt( - output, - "layout(set = %d, binding = %d, std140) uniform VshUniforms {\n" - "%s" - "};\n\n", - opts.ubo_set, opts.ubo_binding, mstring_get_str(uniforms)); - } else { - mstring_append_fmt( - output, - "layout(binding = %d, std140) uniform VshUniforms {\n" - "%s" - "};\n\n", - opts.ubo_binding, mstring_get_str(uniforms)); + mstring_append_fmt(output, + "layout(push_constant) uniform PushConstants {\n" + " vec4 inlineValue[%d];\n" + "};\n\n", + num_uniform_attrs); } + mstring_append_fmt( + output, + "layout(binding = %d, std140) uniform VshUniforms {\n" + "%s" + "};\n\n", + opts.ubo_binding, mstring_get_str(uniforms)); } else { mstring_append( output, mstring_get_str(uniforms)); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.h b/hw/xbox/nv2a/pgraph/glsl/vsh.h index 43d46a0f57..faabd9bbd5 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.h +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.h @@ -103,8 +103,6 @@ typedef struct GenVshGlslOptions { bool prefix_outputs; bool use_push_constants_for_uniform_attrs; int ubo_binding; - int ubo_set; - int vertex_push_offset; } GenVshGlslOptions; MString *pgraph_glsl_gen_vsh(const VshState *state, diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index abc9b597e9..c97d0f6a5a 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -40,114 +40,6 @@ pgraph_reg_w(pg, reg, rv); \ } while (0) -uint8_t pgraph_reg_category_table[0x2000 / 4] = { 0 }; -uint32_t pgraph_reg_dynamic_mask_table[0x2000 / 4] = { 0 }; - -void pgraph_init_reg_dynamic_masks(bool eds1, bool eds3) -{ - memset(pgraph_reg_dynamic_mask_table, 0, - sizeof(pgraph_reg_dynamic_mask_table)); - - pgraph_reg_dynamic_mask_table[NV_PGRAPH_SETUPRASTER / 4] |= - NV_PGRAPH_SETUPRASTER_CULLENABLE | - NV_PGRAPH_SETUPRASTER_CULLCTRL | - NV_PGRAPH_SETUPRASTER_FRONTFACE | - NV_PGRAPH_SETUPRASTER_POFFSETPOINTENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETLINEENABLE | - NV_PGRAPH_SETUPRASTER_POFFSETFILLENABLE | - NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE | - NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE; - - pgraph_reg_dynamic_mask_table[NV_PGRAPH_BLENDCOLOR / 4] = 0xFFFFFFFF; - - pgraph_reg_dynamic_mask_table[NV_PGRAPH_CONTROL_0 / 4] |= - NV_PGRAPH_CONTROL_0_ALPHAREF | - NV_PGRAPH_CONTROL_0_DITHERENABLE; - - pgraph_reg_dynamic_mask_table[NV_PGRAPH_BLEND / 4] |= - NV_PGRAPH_BLEND_LOGICOP_ENABLE | NV_PGRAPH_BLEND_LOGICOP; - - if (eds1) { - pgraph_reg_dynamic_mask_table[NV_PGRAPH_CONTROL_0 / 4] |= - NV_PGRAPH_CONTROL_0_ZENABLE | - NV_PGRAPH_CONTROL_0_ZWRITEENABLE | - NV_PGRAPH_CONTROL_0_STENCIL_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_ZFUNC; - pgraph_reg_dynamic_mask_table[NV_PGRAPH_CONTROL_1 / 4] |= - NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE | - NV_PGRAPH_CONTROL_1_STENCIL_FUNC | - NV_PGRAPH_CONTROL_1_STENCIL_REF | - NV_PGRAPH_CONTROL_1_STENCIL_MASK_READ | - NV_PGRAPH_CONTROL_1_STENCIL_MASK_WRITE; - pgraph_reg_dynamic_mask_table[NV_PGRAPH_CONTROL_2 / 4] |= - NV_PGRAPH_CONTROL_2_STENCIL_OP_FAIL | - NV_PGRAPH_CONTROL_2_STENCIL_OP_ZFAIL | - NV_PGRAPH_CONTROL_2_STENCIL_OP_ZPASS; - } else { - pgraph_reg_dynamic_mask_table[NV_PGRAPH_CONTROL_1 / 4] |= - NV_PGRAPH_CONTROL_1_STENCIL_REF | - NV_PGRAPH_CONTROL_1_STENCIL_MASK_READ | - NV_PGRAPH_CONTROL_1_STENCIL_MASK_WRITE; - } - - if (eds3) { - pgraph_reg_dynamic_mask_table[NV_PGRAPH_BLEND / 4] = 0xFFFFFFFF; - pgraph_reg_dynamic_mask_table[NV_PGRAPH_CONTROL_0 / 4] |= - NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE; - } -} - -static void pgraph_init_reg_category_table(void) -{ - unsigned int shader_regs[] = { - NV_PGRAPH_COMBINECTL, NV_PGRAPH_COMBINESPECFOG0, - NV_PGRAPH_COMBINESPECFOG1, NV_PGRAPH_CONTROL_0, - NV_PGRAPH_CONTROL_3, NV_PGRAPH_CSV0_C, - NV_PGRAPH_CSV0_D, NV_PGRAPH_CSV1_A, - NV_PGRAPH_CSV1_B, NV_PGRAPH_POINTSIZE, - NV_PGRAPH_SETUPRASTER, NV_PGRAPH_SHADERCLIPMODE, - NV_PGRAPH_SHADERCTL, NV_PGRAPH_SHADERPROG, - NV_PGRAPH_SHADOWCTL, NV_PGRAPH_ZCOMPRESSOCCLUDE, - }; - for (int i = 0; i < ARRAY_SIZE(shader_regs); i++) - pgraph_reg_category_table[shader_regs[i] / 4] |= REG_CAT_SHADER; - - for (int i = 0; i < 8; i++) { - pgraph_reg_category_table[(NV_PGRAPH_COMBINEALPHAI0 + i * 4) / 4] |= REG_CAT_SHADER; - pgraph_reg_category_table[(NV_PGRAPH_COMBINEALPHAO0 + i * 4) / 4] |= REG_CAT_SHADER; - pgraph_reg_category_table[(NV_PGRAPH_COMBINECOLORI0 + i * 4) / 4] |= REG_CAT_SHADER; - pgraph_reg_category_table[(NV_PGRAPH_COMBINECOLORO0 + i * 4) / 4] |= REG_CAT_SHADER; - } - - for (int i = 0; i < 4; i++) { - pgraph_reg_category_table[(NV_PGRAPH_TEXCTL0_0 + i * 4) / 4] |= REG_CAT_SHADER; - pgraph_reg_category_table[(NV_PGRAPH_TEXFILTER0 + i * 4) / 4] |= REG_CAT_SHADER; - pgraph_reg_category_table[(NV_PGRAPH_TEXFMT0 + i * 4) / 4] |= REG_CAT_SHADER; - } - - for (int i = 0; i < 4; i++) { - pgraph_reg_category_table[(NV_PGRAPH_TEXOFFSET0 + i * 4) / 4] |= REG_CAT_TEXTURE; - pgraph_reg_category_table[(NV_PGRAPH_TEXFMT0 + i * 4) / 4] |= REG_CAT_TEXTURE; - pgraph_reg_category_table[(NV_PGRAPH_TEXCTL0_0 + i * 4) / 4] |= REG_CAT_TEXTURE; - pgraph_reg_category_table[(NV_PGRAPH_TEXCTL1_0 + i * 4) / 4] |= REG_CAT_TEXTURE; - pgraph_reg_category_table[(NV_PGRAPH_TEXFILTER0 + i * 4) / 4] |= REG_CAT_TEXTURE; - pgraph_reg_category_table[(NV_PGRAPH_TEXIMAGERECT0 + i * 4) / 4] |= REG_CAT_TEXTURE; - pgraph_reg_category_table[(NV_PGRAPH_TEXPALETTE0 + i * 4) / 4] |= REG_CAT_TEXTURE; - } - - unsigned int pipeline_regs[] = { - NV_PGRAPH_BLEND, NV_PGRAPH_CONTROL_0, - NV_PGRAPH_CONTROL_1, NV_PGRAPH_CONTROL_2, - NV_PGRAPH_CONTROL_3, NV_PGRAPH_SETUPRASTER, - NV_PGRAPH_BLENDCOLOR, NV_PGRAPH_ZOFFSETBIAS, - NV_PGRAPH_ZOFFSETFACTOR, - }; - for (int i = 0; i < ARRAY_SIZE(pipeline_regs); i++) - pgraph_reg_category_table[pipeline_regs[i] / 4] |= REG_CAT_PIPELINE; -} NV2AState *g_nv2a; @@ -339,8 +231,6 @@ void pgraph_renderer_register(const PGRAPHRenderer *renderer) void pgraph_init(NV2AState *d) { g_nv2a = d; - pgraph_init_reg_category_table(); - pgraph_init_reg_dynamic_masks(false, false); PGRAPHState *pg = &d->pgraph; qemu_mutex_init(&pg->lock); @@ -513,7 +403,6 @@ static bool attempt_renderer_init(PGRAPHState *pg) static void init_renderer(PGRAPHState *pg) { CONFIG_DISPLAY_RENDERER original_renderer = g_config.display.renderer; - CONFIG_DISPLAY_RENDERER default_renderer = get_default_renderer(); CONFIG_DISPLAY_RENDERER attempts[CONFIG_DISPLAY_RENDERER__COUNT]; int attempt_count = 0; @@ -540,9 +429,8 @@ static void init_renderer(PGRAPHState *pg) g_config.display.renderer = attempts[i]; if (attempt_renderer_init(pg)) { if (attempts[i] != original_renderer) { - g_autofree gchar *msg = - g_strdup_printf("Switched renderer to %s", - pg->renderer->name); + g_autofree gchar *msg = g_strdup_printf( + "Switched renderer to %s", pg->renderer->name); xemu_queue_notification(msg); } return; @@ -1207,14 +1095,8 @@ DEF_METHOD(NV097, SET_SURFACE_FORMAT) pg->surface_shape.color_format = GET_MASK(parameter, NV097_SET_SURFACE_FORMAT_COLOR); - uint32_t old_zeta_format = pg->surface_shape.zeta_format; pg->surface_shape.zeta_format = GET_MASK(parameter, NV097_SET_SURFACE_FORMAT_ZETA); - if (pg->surface_shape.zeta_format != old_zeta_format) { - pg->shader_state_gen++; - pg->non_dynamic_reg_gen++; - pg->any_reg_gen++; - } pg->surface_shape.anti_aliasing = GET_MASK(parameter, NV097_SET_SURFACE_FORMAT_ANTI_ALIASING); pg->surface_shape.log_width = @@ -1276,9 +1158,7 @@ DEF_METHOD(NV097, SET_COMBINER_SPECULAR_FOG_CW1) DEF_METHOD(NV097, SET_TEXTURE_ADDRESS) { int slot = (method - NV097_SET_TEXTURE_ADDRESS) / 64; - unsigned int reg = NV_PGRAPH_TEXADDRESS0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_TEXADDRESS0 + slot * 4, parameter); } DEF_METHOD(NV097, SET_CONTROL0) @@ -1858,7 +1738,6 @@ DEF_METHOD_INC(NV097, SET_MATERIAL_EMISSION) { int slot = (method - NV097_SET_MATERIAL_EMISSION) / 4; // FIXME: Verify NV_IGRAPH_XF_LTCTXA_CM_COL is correct - pg->ltctxa_any_dirty |= (parameter != pg->ltctxa[NV_IGRAPH_XF_LTCTXA_CM_COL][slot]); pg->ltctxa[NV_IGRAPH_XF_LTCTXA_CM_COL][slot] = parameter; pg->ltctxa_dirty[NV_IGRAPH_XF_LTCTXA_CM_COL] = true; } @@ -1948,11 +1827,6 @@ DEF_METHOD(NV097, SET_TEXGEN_Q) DEF_METHOD_INC(NV097, SET_TEXTURE_MATRIX_ENABLE) { int slot = (method - NV097_SET_TEXTURE_MATRIX_ENABLE) / 4; - if (pg->texture_matrix_enable[slot] != parameter) { - pg->shader_state_gen++; - pg->non_dynamic_reg_gen++; - pg->any_reg_gen++; - } pg->texture_matrix_enable[slot] = parameter; } @@ -1970,7 +1844,6 @@ DEF_METHOD_INC(NV097, SET_PROJECTION_MATRIX) int slot = (method - NV097_SET_PROJECTION_MATRIX) / 4; // pg->projection_matrix[slot] = *(float*)¶meter; unsigned int row = NV_IGRAPH_XF_XFCTX_PMAT0 + slot/4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[row][slot%4]); pg->vsh_constants[row][slot%4] = parameter; pg->vsh_constants_dirty[row] = true; } @@ -1981,7 +1854,6 @@ DEF_METHOD_INC(NV097, SET_MODEL_VIEW_MATRIX) unsigned int matnum = slot / 16; unsigned int entry = slot % 16; unsigned int row = NV_IGRAPH_XF_XFCTX_MMAT0 + matnum*8 + entry/4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[row][entry % 4]); pg->vsh_constants[row][entry % 4] = parameter; pg->vsh_constants_dirty[row] = true; } @@ -1992,7 +1864,6 @@ DEF_METHOD_INC(NV097, SET_INVERSE_MODEL_VIEW_MATRIX) unsigned int matnum = slot / 16; unsigned int entry = slot % 16; unsigned int row = NV_IGRAPH_XF_XFCTX_IMMAT0 + matnum*8 + entry/4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[row][entry % 4]); pg->vsh_constants[row][entry % 4] = parameter; pg->vsh_constants_dirty[row] = true; } @@ -2001,7 +1872,6 @@ DEF_METHOD_INC(NV097, SET_COMPOSITE_MATRIX) { int slot = (method - NV097_SET_COMPOSITE_MATRIX) / 4; unsigned int row = NV_IGRAPH_XF_XFCTX_CMAT0 + slot/4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[row][slot%4]); pg->vsh_constants[row][slot%4] = parameter; pg->vsh_constants_dirty[row] = true; } @@ -2012,7 +1882,6 @@ DEF_METHOD_INC(NV097, SET_TEXTURE_MATRIX) unsigned int tex = slot / 16; unsigned int entry = slot % 16; unsigned int row = NV_IGRAPH_XF_XFCTX_T0MAT + tex*8 + entry/4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[row][entry%4]); pg->vsh_constants[row][entry%4] = parameter; pg->vsh_constants_dirty[row] = true; } @@ -2026,7 +1895,6 @@ DEF_METHOD_INC(NV097, SET_FOG_PARAMS) /* FIXME: No idea where slot = 2 is */ } - pg->ltctxa_any_dirty |= (parameter != pg->ltctxa[NV_IGRAPH_XF_LTCTXA_FOG_K][slot]); pg->ltctxa[NV_IGRAPH_XF_LTCTXA_FOG_K][slot] = parameter; pg->ltctxa_dirty[NV_IGRAPH_XF_LTCTXA_FOG_K] = true; } @@ -2038,7 +1906,6 @@ DEF_METHOD_INC(NV097, SET_TEXGEN_PLANE_S) unsigned int tex = slot / 16; unsigned int entry = slot % 16; unsigned int row = NV_IGRAPH_XF_XFCTX_TG0MAT + tex*8 + entry/4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[row][entry%4]); pg->vsh_constants[row][entry%4] = parameter; pg->vsh_constants_dirty[row] = true; } @@ -2052,7 +1919,6 @@ DEF_METHOD(NV097, SET_TEXGEN_VIEW_MODEL) DEF_METHOD_INC(NV097, SET_FOG_PLANE) { int slot = (method - NV097_SET_FOG_PLANE) / 4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[NV_IGRAPH_XF_XFCTX_FOG][slot]); pg->vsh_constants[NV_IGRAPH_XF_XFCTX_FOG][slot] = parameter; pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_FOG] = true; } @@ -2160,13 +2026,7 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS) int slot = (method - NV097_SET_SPECULAR_PARAMS) / 4; pg->specular_params[slot] = *(float *)¶meter; if (slot == 5) { - float new_power = reconstruct_specular_power(pg->specular_params); - if (pg->specular_power != new_power) { - pg->shader_state_gen++; - pg->non_dynamic_reg_gen++; - pg->any_reg_gen++; - } - pg->specular_power = new_power; + pg->specular_power = reconstruct_specular_power(pg->specular_params); } } @@ -2174,7 +2034,6 @@ DEF_METHOD_INC(NV097, SET_SCENE_AMBIENT_COLOR) { int slot = (method - NV097_SET_SCENE_AMBIENT_COLOR) / 4; // ?? - pg->ltctxa_any_dirty |= (parameter != pg->ltctxa[NV_IGRAPH_XF_LTCTXA_FR_AMB][slot]); pg->ltctxa[NV_IGRAPH_XF_LTCTXA_FR_AMB][slot] = parameter; pg->ltctxa_dirty[NV_IGRAPH_XF_LTCTXA_FR_AMB] = true; } @@ -2182,7 +2041,6 @@ DEF_METHOD_INC(NV097, SET_SCENE_AMBIENT_COLOR) DEF_METHOD_INC(NV097, SET_VIEWPORT_OFFSET) { int slot = (method - NV097_SET_VIEWPORT_OFFSET) / 4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][slot]); pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][slot] = parameter; pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_VPOFF] = true; } @@ -2190,19 +2048,12 @@ DEF_METHOD_INC(NV097, SET_VIEWPORT_OFFSET) DEF_METHOD_INC(NV097, SET_POINT_PARAMS) { int slot = (method - NV097_SET_POINT_PARAMS) / 4; - float new_val = *(float *)¶meter; - if (pg->point_params[slot] != new_val) { - pg->shader_state_gen++; - pg->non_dynamic_reg_gen++; - pg->any_reg_gen++; - } - pg->point_params[slot] = new_val; /* FIXME: Where? */ + pg->point_params[slot] = *(float *)¶meter; /* FIXME: Where? */ } DEF_METHOD_INC(NV097, SET_EYE_POSITION) { int slot = (method - NV097_SET_EYE_POSITION) / 4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[NV_IGRAPH_XF_XFCTX_EYEP][slot]); pg->vsh_constants[NV_IGRAPH_XF_XFCTX_EYEP][slot] = parameter; pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_EYEP] = true; } @@ -2240,7 +2091,6 @@ DEF_METHOD_INC(NV097, SET_COLOR_KEY_COLOR) DEF_METHOD_INC(NV097, SET_VIEWPORT_SCALE) { int slot = (method - NV097_SET_VIEWPORT_SCALE) / 4; - pg->vsh_constants_any_dirty |= (parameter != pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPSCL][slot]); pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPSCL][slot] = parameter; pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_VPSCL] = true; } @@ -2272,9 +2122,6 @@ DEF_METHOD_INC(NV097, SET_TRANSFORM_CONSTANT) // VertexShaderConstant *constant = &pg->constants[const_load]; pg->vsh_constants_dirty[const_load] |= (parameter != pg->vsh_constants[const_load][slot%4]); - if (parameter != pg->vsh_constants[const_load][slot % 4]) { - pg->vsh_constants_any_dirty = true; - } pg->vsh_constants[const_load][slot%4] = parameter; if (slot % 4 == 3) { @@ -2307,27 +2154,18 @@ DEF_METHOD_INC(NV097, SET_BACK_LIGHT_AMBIENT_COLOR) case NV097_SET_BACK_LIGHT_AMBIENT_COLOR ... NV097_SET_BACK_LIGHT_AMBIENT_COLOR + 8: part -= NV097_SET_BACK_LIGHT_AMBIENT_COLOR / 4; - pg->ltctxb_any_dirty |= - (parameter != - pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_BAMB + slot * 6][part]); pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_BAMB + slot*6][part] = parameter; pg->ltctxb_dirty[NV_IGRAPH_XF_LTCTXB_L0_BAMB + slot*6] = true; break; case NV097_SET_BACK_LIGHT_DIFFUSE_COLOR ... NV097_SET_BACK_LIGHT_DIFFUSE_COLOR + 8: part -= NV097_SET_BACK_LIGHT_DIFFUSE_COLOR / 4; - pg->ltctxb_any_dirty |= - (parameter != - pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_BDIF + slot * 6][part]); pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_BDIF + slot*6][part] = parameter; pg->ltctxb_dirty[NV_IGRAPH_XF_LTCTXB_L0_BDIF + slot*6] = true; break; case NV097_SET_BACK_LIGHT_SPECULAR_COLOR ... NV097_SET_BACK_LIGHT_SPECULAR_COLOR + 8: part -= NV097_SET_BACK_LIGHT_SPECULAR_COLOR / 4; - pg->ltctxb_any_dirty |= - (parameter != - pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_BSPC + slot * 6][part]); pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_BSPC + slot*6][part] = parameter; pg->ltctxb_dirty[NV_IGRAPH_XF_LTCTXB_L0_BSPC + slot*6] = true; break; @@ -2348,33 +2186,22 @@ DEF_METHOD_INC(NV097, SET_LIGHT_AMBIENT_COLOR) case NV097_SET_LIGHT_AMBIENT_COLOR ... NV097_SET_LIGHT_AMBIENT_COLOR + 8: part -= NV097_SET_LIGHT_AMBIENT_COLOR / 4; - pg->ltctxb_any_dirty |= - (parameter != - pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_AMB + slot * 6][part]); pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_AMB + slot*6][part] = parameter; pg->ltctxb_dirty[NV_IGRAPH_XF_LTCTXB_L0_AMB + slot*6] = true; break; case NV097_SET_LIGHT_DIFFUSE_COLOR ... NV097_SET_LIGHT_DIFFUSE_COLOR + 8: part -= NV097_SET_LIGHT_DIFFUSE_COLOR / 4; - pg->ltctxb_any_dirty |= - (parameter != - pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_DIF + slot * 6][part]); pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_DIF + slot*6][part] = parameter; pg->ltctxb_dirty[NV_IGRAPH_XF_LTCTXB_L0_DIF + slot*6] = true; break; case NV097_SET_LIGHT_SPECULAR_COLOR ... NV097_SET_LIGHT_SPECULAR_COLOR + 8: part -= NV097_SET_LIGHT_SPECULAR_COLOR / 4; - pg->ltctxb_any_dirty |= - (parameter != - pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_SPC + slot * 6][part]); pg->ltctxb[NV_IGRAPH_XF_LTCTXB_L0_SPC + slot*6][part] = parameter; pg->ltctxb_dirty[NV_IGRAPH_XF_LTCTXB_L0_SPC + slot*6] = true; break; case NV097_SET_LIGHT_LOCAL_RANGE: - pg->ltc1_any_dirty |= - (parameter != pg->ltc1[NV_IGRAPH_XF_LTC1_r0 + slot][0]); pg->ltc1[NV_IGRAPH_XF_LTC1_r0 + slot][0] = parameter; pg->ltc1_dirty[NV_IGRAPH_XF_LTC1_r0 + slot] = true; break; @@ -2391,18 +2218,12 @@ DEF_METHOD_INC(NV097, SET_LIGHT_AMBIENT_COLOR) case NV097_SET_LIGHT_SPOT_FALLOFF ... NV097_SET_LIGHT_SPOT_FALLOFF + 8: part -= NV097_SET_LIGHT_SPOT_FALLOFF / 4; - pg->ltctxa_any_dirty |= - (parameter != - pg->ltctxa[NV_IGRAPH_XF_LTCTXA_L0_K + slot * 2][part]); pg->ltctxa[NV_IGRAPH_XF_LTCTXA_L0_K + slot*2][part] = parameter; pg->ltctxa_dirty[NV_IGRAPH_XF_LTCTXA_L0_K + slot*2] = true; break; case NV097_SET_LIGHT_SPOT_DIRECTION ... NV097_SET_LIGHT_SPOT_DIRECTION + 12: part -= NV097_SET_LIGHT_SPOT_DIRECTION / 4; - pg->ltctxa_any_dirty |= - (parameter != - pg->ltctxa[NV_IGRAPH_XF_LTCTXA_L0_SPT + slot * 2][part]); pg->ltctxa[NV_IGRAPH_XF_LTCTXA_L0_SPT + slot*2][part] = parameter; pg->ltctxa_dirty[NV_IGRAPH_XF_LTCTXA_L0_SPT + slot*2] = true; break; @@ -2666,10 +2487,8 @@ DEF_METHOD_INC(NV097, SET_TEXCOORD3_2F) do { \ VertexAttribute *attribute = &pg->vertex_attributes[(attr_index)]; \ pgraph_allocate_inline_buffer_vertices(pg, (attr_index)); \ - attribute->inline_value[0] = (parameter & 0xFF) / 255.0f; \ - attribute->inline_value[1] = ((parameter >> 8) & 0xFF) / 255.0f; \ - attribute->inline_value[2] = ((parameter >> 16) & 0xFF) / 255.0f; \ - attribute->inline_value[3] = ((parameter >> 24) & 0xFF) / 255.0f; \ + pgraph_argb_pack32_to_rgba_float(parameter, \ + attribute->inline_value); \ } while (0) DEF_METHOD_INC(NV097, SET_DIFFUSE_COLOR4UB) @@ -2731,8 +2550,6 @@ DEF_METHOD_INC(NV097, SET_VERTEX_DATA_ARRAY_FORMAT) } else { pg->compressed_attrs &= ~(1 << slot); } - - pg->vertex_attr_gen++; } DEF_METHOD_INC(NV097, SET_VERTEX_DATA_ARRAY_OFFSET) @@ -2741,8 +2558,6 @@ DEF_METHOD_INC(NV097, SET_VERTEX_DATA_ARRAY_OFFSET) pg->vertex_attributes[slot].dma_select = parameter & 0x80000000; pg->vertex_attributes[slot].offset = parameter & 0x7fffffff; - - pg->vertex_attr_gen++; } DEF_METHOD(NV097, SET_LOGIC_OP_ENABLE) @@ -2778,7 +2593,6 @@ DEF_METHOD(NV097, GET_REPORT) DEF_METHOD_INC(NV097, SET_EYE_DIRECTION) { int slot = (method - NV097_SET_EYE_DIRECTION) / 4; - pg->ltctxa_any_dirty |= (parameter != pg->ltctxa[NV_IGRAPH_XF_LTCTXA_EYED][slot]); pg->ltctxa[NV_IGRAPH_XF_LTCTXA_EYED][slot] = parameter; pg->ltctxa_dirty[NV_IGRAPH_XF_LTCTXA_EYED] = true; } @@ -2810,9 +2624,8 @@ DEF_METHOD(NV097, SET_BEGIN_END) DEF_METHOD(NV097, SET_TEXTURE_OFFSET) { int slot = (method - NV097_SET_TEXTURE_OFFSET) / 64; - unsigned int reg = NV_PGRAPH_TEXOFFSET0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_TEXOFFSET0 + slot * 4, parameter); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_FORMAT) @@ -2839,7 +2652,6 @@ DEF_METHOD(NV097, SET_TEXTURE_FORMAT) GET_MASK(parameter, NV097_SET_TEXTURE_FORMAT_BASE_SIZE_P); unsigned int reg = NV_PGRAPH_TEXFMT0 + slot * 4; - uint32_t prev = pgraph_reg_r(pg, reg); PG_SET_MASK(reg, NV_PGRAPH_TEXFMT0_CONTEXT_DMA, dma_select); PG_SET_MASK(reg, NV_PGRAPH_TEXFMT0_CUBEMAPENABLE, cubemap); PG_SET_MASK(reg, NV_PGRAPH_TEXFMT0_BORDER_SOURCE, border_source); @@ -2850,39 +2662,35 @@ DEF_METHOD(NV097, SET_TEXTURE_FORMAT) PG_SET_MASK(reg, NV_PGRAPH_TEXFMT0_BASE_SIZE_V, log_height); PG_SET_MASK(reg, NV_PGRAPH_TEXFMT0_BASE_SIZE_P, log_depth); - pg->texture_dirty[slot] |= (pgraph_reg_r(pg, reg) != prev); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_CONTROL0) { int slot = (method - NV097_SET_TEXTURE_CONTROL0) / 64; - unsigned int reg = NV_PGRAPH_TEXCTL0_0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_TEXCTL0_0 + slot*4, parameter); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_CONTROL1) { int slot = (method - NV097_SET_TEXTURE_CONTROL1) / 64; - unsigned int reg = NV_PGRAPH_TEXCTL1_0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_TEXCTL1_0 + slot*4, parameter); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_FILTER) { int slot = (method - NV097_SET_TEXTURE_FILTER) / 64; - unsigned int reg = NV_PGRAPH_TEXFILTER0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_TEXFILTER0 + slot * 4, parameter); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_IMAGE_RECT) { int slot = (method - NV097_SET_TEXTURE_IMAGE_RECT) / 64; - unsigned int reg = NV_PGRAPH_TEXIMAGERECT0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_TEXIMAGERECT0 + slot * 4, parameter); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_PALETTE) @@ -2897,20 +2705,17 @@ DEF_METHOD(NV097, SET_TEXTURE_PALETTE) GET_MASK(parameter, NV097_SET_TEXTURE_PALETTE_OFFSET); unsigned int reg = NV_PGRAPH_TEXPALETTE0 + slot * 4; - uint32_t prev = pgraph_reg_r(pg, reg); PG_SET_MASK(reg, NV_PGRAPH_TEXPALETTE0_CONTEXT_DMA, dma_select); PG_SET_MASK(reg, NV_PGRAPH_TEXPALETTE0_LENGTH, length); PG_SET_MASK(reg, NV_PGRAPH_TEXPALETTE0_OFFSET, offset); - pg->texture_dirty[slot] |= (pgraph_reg_r(pg, reg) != prev); + pg->texture_dirty[slot] = true; } DEF_METHOD(NV097, SET_TEXTURE_BORDER_COLOR) { int slot = (method - NV097_SET_TEXTURE_BORDER_COLOR) / 64; - unsigned int reg = NV_PGRAPH_BORDERCOLOR0 + slot * 4; - pg->texture_dirty[slot] |= (parameter != pgraph_reg_r(pg, reg)); - pgraph_reg_w(pg, reg, parameter); + pgraph_reg_w(pg, NV_PGRAPH_BORDERCOLOR0 + slot * 4, parameter); } DEF_METHOD(NV097, SET_TEXTURE_SET_BUMP_ENV_MAT) @@ -3110,10 +2915,14 @@ DEF_METHOD_INC(NV097, SET_VERTEX_DATA4UB) int slot = (method - NV097_SET_VERTEX_DATA4UB) / 4; VertexAttribute *attribute = &pg->vertex_attributes[slot]; pgraph_allocate_inline_buffer_vertices(pg, slot); - attribute->inline_value[0] = (parameter & 0xFF) / 255.0; - attribute->inline_value[1] = ((parameter >> 8) & 0xFF) / 255.0; - attribute->inline_value[2] = ((parameter >> 16) & 0xFF) / 255.0; - attribute->inline_value[3] = ((parameter >> 24) & 0xFF) / 255.0; + if (slot == NV2A_VERTEX_ATTR_DIFFUSE || slot == NV2A_VERTEX_ATTR_SPECULAR) { + pgraph_argb_pack32_to_rgba_float(parameter, attribute->inline_value); + } else { + attribute->inline_value[0] = (parameter & 0xFF) / 255.0; + attribute->inline_value[1] = ((parameter >> 8) & 0xFF) / 255.0; + attribute->inline_value[2] = ((parameter >> 16) & 0xFF) / 255.0; + attribute->inline_value[3] = ((parameter >> 24) & 0xFF) / 255.0; + } if (slot == 0) { pgraph_finish_inline_buffer_vertex(pg); } @@ -3223,13 +3032,7 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS_BACK) int slot = (method - NV097_SET_SPECULAR_PARAMS_BACK) / 4; pg->specular_params_back[slot] = *(float *)¶meter; if (slot == 5) { - float new_power = reconstruct_specular_power(pg->specular_params_back); - if (pg->specular_power_back != new_power) { - pg->shader_state_gen++; - pg->non_dynamic_reg_gen++; - pg->any_reg_gen++; - } - pg->specular_power_back = new_power; + pg->specular_power_back = reconstruct_specular_power(pg->specular_params_back); } } @@ -3301,12 +3104,6 @@ DEF_METHOD(NV097, LAUNCH_TRANSFORM_PROGRAM) memcpy(state_linkage.input_regs, pg->vertex_state_shader_v0, sizeof(pg->vertex_state_shader_v0)); nv2a_vsh_emu_execute_track_context_writes(&state, &program, pg->vsh_constants_dirty); - for (int i = 0; i < NV2A_VERTEXSHADER_CONSTANTS; i++) { - if (pg->vsh_constants_dirty[i]) { - pg->vsh_constants_any_dirty = true; - break; - } - } nv2a_vsh_program_destroy(&program); } diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index 31105fcd7e..49f941d450 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -158,12 +158,6 @@ typedef struct PGRAPHState { hwaddr dma_a, dma_b; bool texture_dirty[NV2A_MAX_TEXTURES]; - uint32_t texture_state_gen; - uint32_t vertex_attr_gen; - uint32_t shader_state_gen; - uint32_t pipeline_state_gen; - uint32_t any_reg_gen; - uint32_t non_dynamic_reg_gen; bool texture_matrix_enable[NV2A_MAX_TEXTURES]; @@ -187,18 +181,14 @@ typedef struct PGRAPHState { uint32_t vsh_constants[NV2A_VERTEXSHADER_CONSTANTS][4]; bool vsh_constants_dirty[NV2A_VERTEXSHADER_CONSTANTS]; - bool vsh_constants_any_dirty; /* lighting constant arrays */ uint32_t ltctxa[NV2A_LTCTXA_COUNT][4]; bool ltctxa_dirty[NV2A_LTCTXA_COUNT]; - bool ltctxa_any_dirty; uint32_t ltctxb[NV2A_LTCTXB_COUNT][4]; bool ltctxb_dirty[NV2A_LTCTXB_COUNT]; - bool ltctxb_any_dirty; uint32_t ltc1[NV2A_LTC1_COUNT][4]; bool ltc1_dirty[NV2A_LTC1_COUNT]; - bool ltc1_any_dirty; float material_alpha; @@ -300,13 +290,6 @@ extern NV2AState *g_nv2a; // FIXME: Add new function pgraph_is_texture_sampler_active() -#define REG_CAT_SHADER (1 << 0) -#define REG_CAT_PIPELINE (1 << 1) -#define REG_CAT_TEXTURE (1 << 2) -extern uint8_t pgraph_reg_category_table[]; -extern uint32_t pgraph_reg_dynamic_mask_table[]; -void pgraph_init_reg_dynamic_masks(bool eds1, bool eds3); - static inline uint32_t pgraph_reg_r(PGRAPHState *pg, unsigned int r) { assert(r % 4 == 0); @@ -316,23 +299,8 @@ static inline uint32_t pgraph_reg_r(PGRAPHState *pg, unsigned int r) static inline void pgraph_reg_w(PGRAPHState *pg, unsigned int r, uint32_t v) { assert(r % 4 == 0); - uint32_t old = pg->regs_[r]; - if (old != v) { + if (pg->regs_[r] != v) { bitmap_set(pg->regs_dirty, r / sizeof(uint32_t), 1); - uint8_t cat = pgraph_reg_category_table[r / 4]; - uint32_t dyn_mask = pgraph_reg_dynamic_mask_table[r / 4]; - uint32_t non_dyn_changed = (old ^ v) & ~dyn_mask; - if (cat & REG_CAT_SHADER) { - pg->shader_state_gen++; - } - if (cat & REG_CAT_PIPELINE) { - pg->pipeline_state_gen++; - } - if (cat & REG_CAT_TEXTURE) pg->texture_state_gen++; - bool tex_only = cat && !(cat & ~REG_CAT_TEXTURE); - if (non_dyn_changed && !tex_only) - pg->non_dynamic_reg_gen++; - pg->any_reg_gen++; } pg->regs_[r] = v; } @@ -344,14 +312,6 @@ static inline bool pgraph_is_reg_dirty(PGRAPHState *pg, unsigned int reg) return test_bit(reg / sizeof(uint32_t), pg->regs_dirty); } -static inline bool pgraph_has_dirty_regs(PGRAPHState *pg) -{ - for (int i = 0; i < ARRAY_SIZE(pg->regs_dirty); i++) { - if (pg->regs_dirty[i]) return true; - } - return false; -} - static inline bool pgraph_is_texture_stage_active(PGRAPHState *pg, unsigned int stage) { assert(stage < NV2A_MAX_TEXTURES); diff --git a/hw/xbox/nv2a/pgraph/profile.c b/hw/xbox/nv2a/pgraph/profile.c index a70c27562f..295042010d 100644 --- a/hw/xbox/nv2a/pgraph/profile.c +++ b/hw/xbox/nv2a/pgraph/profile.c @@ -23,11 +23,6 @@ NV2AStats g_nv2a_stats; void nv2a_profile_increment(void) { -#ifdef __ANDROID__ - /* Skip profiling on Android — stats are never displayed and the - * qemu_clock_get_us() syscalls add unnecessary overhead per frame. */ - return; -#else int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME); const int64_t fps_update_interval = 250000; g_nv2a_stats.last_flip_time = now; @@ -42,14 +37,10 @@ void nv2a_profile_increment(void) ts = now; frame_count = 0; } -#endif } void nv2a_profile_flip_stall(void) { -#ifdef __ANDROID__ - return; -#else int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME); int64_t render_time = (now-g_nv2a_stats.last_flip_time)/1000; @@ -60,7 +51,6 @@ void nv2a_profile_flip_stall(void) (g_nv2a_stats.frame_ptr + 1) % NV2A_PROF_NUM_FRAMES; g_nv2a_stats.frame_count++; memset(&g_nv2a_stats.frame_working, 0, sizeof(g_nv2a_stats.frame_working)); -#endif } const char *nv2a_profile_get_counter_name(unsigned int cnt) diff --git a/hw/xbox/nv2a/pgraph/rdi.c b/hw/xbox/nv2a/pgraph/rdi.c index 1994f1b80c..f540b240c6 100644 --- a/hw/xbox/nv2a/pgraph/rdi.c +++ b/hw/xbox/nv2a/pgraph/rdi.c @@ -50,9 +50,6 @@ void pgraph_rdi_write(PGRAPHState *pg, unsigned int select, assert((address / 4) < NV2A_VERTEXSHADER_CONSTANTS); pg->vsh_constants_dirty[address / 4] |= (val != pg->vsh_constants[address / 4][3 - address % 4]); - if (val != pg->vsh_constants[address / 4][3 - address % 4]) { - pg->vsh_constants_any_dirty = true; - } pg->vsh_constants[address / 4][3 - address % 4] = val; break; default: diff --git a/hw/xbox/nv2a/pgraph/swizzle.c b/hw/xbox/nv2a/pgraph/swizzle.c index a6d65f8d42..9ba50f76c4 100644 --- a/hw/xbox/nv2a/pgraph/swizzle.c +++ b/hw/xbox/nv2a/pgraph/swizzle.c @@ -79,12 +79,6 @@ static inline uint32_t swizzle_increment_offset(uint32_t offset, uint32_t mask) } #ifdef __aarch64__ -/* - * In 2D Morton order, each aligned 2x2 RGBA8 block maps to 16 contiguous - * bytes in swizzled memory. That lets us move four pixels at a time with a - * single NEON load/store pair while keeping the existing scalar path for - * everything else. - */ static inline void swizzle_box_neon_2d_rgba8(const uint8_t *src_buf, unsigned int width, unsigned int height, diff --git a/hw/xbox/nv2a/pgraph/texture.h b/hw/xbox/nv2a/pgraph/texture.h index 1d3697a3f1..343bcf0e3b 100644 --- a/hw/xbox/nv2a/pgraph/texture.h +++ b/hw/xbox/nv2a/pgraph/texture.h @@ -50,36 +50,8 @@ typedef struct BasicColorFormatInfo { bool depth; } BasicColorFormatInfo; -enum PgraphTextureSignedChannel { - PGRAPH_TEXTURE_SIGNED_R = 1 << 0, - PGRAPH_TEXTURE_SIGNED_G = 1 << 1, - PGRAPH_TEXTURE_SIGNED_B = 1 << 2, - PGRAPH_TEXTURE_SIGNED_A = 1 << 3, -}; - extern const BasicColorFormatInfo kelvin_color_format_info_map[66]; -static inline uint8_t -pgraph_get_texture_signed_component_mask_from_filter(uint32_t filter) -{ - uint8_t mask = 0; - - if (filter & NV_PGRAPH_TEXFILTER0_RSIGNED) { - mask |= PGRAPH_TEXTURE_SIGNED_R; - } - if (filter & NV_PGRAPH_TEXFILTER0_GSIGNED) { - mask |= PGRAPH_TEXTURE_SIGNED_G; - } - if (filter & NV_PGRAPH_TEXFILTER0_BSIGNED) { - mask |= PGRAPH_TEXTURE_SIGNED_B; - } - if (filter & NV_PGRAPH_TEXFILTER0_ASIGNED) { - mask |= PGRAPH_TEXTURE_SIGNED_A; - } - - return mask; -} - uint8_t *pgraph_convert_texture_data(const TextureShape s, const uint8_t *data, const uint8_t *palette_data, unsigned int width, unsigned int height, diff --git a/hw/xbox/nv2a/pgraph/vk/blit.c b/hw/xbox/nv2a/pgraph/vk/blit.c index 9df86bf539..409f68188a 100644 --- a/hw/xbox/nv2a/pgraph/vk/blit.c +++ b/hw/xbox/nv2a/pgraph/vk/blit.c @@ -224,4 +224,12 @@ void pgraph_vk_image_blit(NV2AState *d) DIRTY_MEMORY_VGA); memory_region_set_client_dirty(d->vram, dest_addr, clipped_dest_size, DIRTY_MEMORY_NV2A_TEX); + /* + * Also mark NV2A surface dirty so that update_surface_part() detects + * the blit write and sets upload_pending on any surface bound at this + * address. Without this, the surface's VkImage retains stale GPU- + * rendered content while VRAM has fresh blit data. + */ + memory_region_set_client_dirty(d->vram, dest_addr, clipped_dest_size, + DIRTY_MEMORY_NV2A); } diff --git a/hw/xbox/nv2a/pgraph/vk/buffer.c b/hw/xbox/nv2a/pgraph/vk/buffer.c index c4ebf87bfe..f8b7bf35b5 100644 --- a/hw/xbox/nv2a/pgraph/vk/buffer.c +++ b/hw/xbox/nv2a/pgraph/vk/buffer.c @@ -23,82 +23,6 @@ #include #endif -typedef struct MemoryBudget { - size_t total_heap; - size_t renderer_budget; - size_t vertex_inline_cap; - size_t index_cap; - size_t staging_cap; - size_t uniform_cap; - size_t shader_module_cache_entries; -} MemoryBudget; - -static MemoryBudget compute_memory_budget(PGRAPHVkState *r) -{ - const size_t mib = 1024 * 1024; - const size_t gib = 1024 * mib; - - VkPhysicalDeviceMemoryProperties const *props; - vmaGetMemoryProperties(r->allocator, &props); - - size_t total_heap = 0; - for (uint32_t i = 0; i < props->memoryHeapCount; i++) { - if (props->memoryHeaps[i].size > total_heap) { - total_heap = props->memoryHeaps[i].size; - } - } - - MemoryBudget b = { .total_heap = total_heap }; - -#ifdef __ANDROID__ - if (total_heap <= 4 * gib) { - b.renderer_budget = 512 * mib; - } else if (total_heap <= 6 * gib) { - b.renderer_budget = 768 * mib; - } else if (total_heap <= 8 * gib) { - b.renderer_budget = 1024 * mib; - } else if (total_heap <= 12 * gib) { - b.renderer_budget = 1536 * mib; - } else if (total_heap <= 16 * gib) { - b.renderer_budget = 2048 * mib; - } else if (total_heap <= 22 * gib) { - b.renderer_budget = 3072 * mib; - } else { - b.renderer_budget = SIZE_MAX; - } -#else - b.renderer_budget = SIZE_MAX; -#endif - - if (b.renderer_budget == SIZE_MAX) { - b.vertex_inline_cap = SIZE_MAX; - b.index_cap = SIZE_MAX; - b.staging_cap = SIZE_MAX; - b.uniform_cap = 64 * mib; - b.shader_module_cache_entries = 50 * 1024; - } else { - size_t budget = b.renderer_budget; - size_t budget_mib = budget / mib; - - b.vertex_inline_cap = MAX(8 * mib, budget / 5); - b.index_cap = MAX(4 * mib, budget / 20); - b.staging_cap = MAX(16 * mib, budget * 12 / 100); - b.uniform_cap = MAX(16 * mib, budget / 16); - if (b.uniform_cap > 64 * mib) { - b.uniform_cap = 64 * mib; - } - b.shader_module_cache_entries = budget_mib * 4; - if (b.shader_module_cache_entries < 2048) { - b.shader_module_cache_entries = 2048; - } - if (b.shader_module_cache_entries > 50 * 1024) { - b.shader_module_cache_entries = 50 * 1024; - } - } - - return b; -} - static const char *const buffer_names[BUFFER_COUNT] = { "BUFFER_STAGING_DST", "BUFFER_STAGING_SRC", @@ -152,14 +76,14 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp) PGRAPHState *pg = &d->pgraph; PGRAPHVkState *r = pg->vk_renderer_state; + // FIXME: Profile buffer sizes + const size_t mib = 1024 * 1024; size_t vram_size = memory_region_size(d->vram); - MemoryBudget mb = compute_memory_budget(r); size_t staging_size = vram_size; if (staging_size < (16 * mib)) { staging_size = 16 * mib; } - staging_size = MIN(staging_size, mb.staging_cap); size_t compute_size = vram_size * 2; if (compute_size < (64 * mib)) { compute_size = 64 * mib; @@ -174,33 +98,10 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp) } #endif - size_t index_size = sizeof(pg->inline_elements) * 100; - index_size = MIN(index_size, mb.index_cap); - - size_t vertex_inline_size = NV2A_VERTEXSHADER_ATTRIBUTES * - NV2A_MAX_BATCH_LENGTH * - 4 * sizeof(float) * 10; - vertex_inline_size = MIN(vertex_inline_size, mb.vertex_inline_cap); - size_t uniform_size = mb.uniform_cap; - - r->shader_module_cache_target = mb.shader_module_cache_entries; - #ifdef __ANDROID__ __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "vk memory budget: heap=%zuMB budget=%s%zuMB staging_cap=%zuMB index_cap=%zuMB vtx_inline_cap=%zuMB uniform_cap=%zuMB shader_cache=%zu", - mb.total_heap >> 20, - mb.renderer_budget == SIZE_MAX ? "uncapped/" : "", - mb.renderer_budget == SIZE_MAX ? 0 : - mb.renderer_budget >> 20, - mb.staging_cap == SIZE_MAX ? 0 : mb.staging_cap >> 20, - mb.index_cap == SIZE_MAX ? 0 : mb.index_cap >> 20, - mb.vertex_inline_cap == SIZE_MAX ? 0 : - mb.vertex_inline_cap >> 20, - mb.uniform_cap >> 20, - mb.shader_module_cache_entries); - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "vk buffer init: vram=%zu staging=%zu compute=%zu uniform=%zu", - vram_size, staging_size, compute_size, uniform_size); + "vk buffer init: vram=%zu staging=%zu compute=%zu", + vram_size, staging_size, compute_size); #endif VmaAllocationCreateInfo host_alloc_create_info = { @@ -242,13 +143,13 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp) .alloc_info = device_alloc_create_info, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - .buffer_size = index_size, + .buffer_size = sizeof(pg->inline_elements) * 100, }; r->storage_buffers[BUFFER_INDEX_STAGING] = (StorageBuffer){ .alloc_info = host_alloc_create_info, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - .buffer_size = index_size, + .buffer_size = r->storage_buffers[BUFFER_INDEX].buffer_size, }; // FIXME: Don't assume that we can render with host mapped buffer @@ -270,20 +171,21 @@ bool pgraph_vk_init_buffers(NV2AState *d, Error **errp) .alloc_info = device_alloc_create_info, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - .buffer_size = vertex_inline_size, + .buffer_size = NV2A_VERTEXSHADER_ATTRIBUTES * NV2A_MAX_BATCH_LENGTH * + 4 * sizeof(float) * 10, }; r->storage_buffers[BUFFER_VERTEX_INLINE_STAGING] = (StorageBuffer){ .alloc_info = host_alloc_create_info, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - .buffer_size = vertex_inline_size, + .buffer_size = r->storage_buffers[BUFFER_VERTEX_INLINE].buffer_size, }; r->storage_buffers[BUFFER_UNIFORM] = (StorageBuffer){ .alloc_info = device_alloc_create_info, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - .buffer_size = uniform_size, + .buffer_size = 8 * 1024 * 1024, }; r->storage_buffers[BUFFER_UNIFORM_STAGING] = (StorageBuffer){ diff --git a/hw/xbox/nv2a/pgraph/vk/compile_worker.c b/hw/xbox/nv2a/pgraph/vk/compile_worker.c deleted file mode 100644 index aa577947dd..0000000000 --- a/hw/xbox/nv2a/pgraph/vk/compile_worker.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Geforce NV2A PGRAPH Vulkan Renderer - Async Compile Worker - * - * Copyright (c) 2024-2025 Matt Borgerson - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ - -#include "renderer.h" -#include "qemu/mstring.h" - -#if OPT_ASYNC_COMPILE - -static void process_shader_module_job(PGRAPHVkState *r, CompileJob *job) -{ - ShaderModuleCacheEntry *target = job->shader_module.target; - ShaderModuleCacheKey *key = &job->shader_module.key; - MString *code; - - switch (key->kind) { - case VK_SHADER_STAGE_VERTEX_BIT: - code = pgraph_glsl_gen_vsh(&key->vsh.state, key->vsh.glsl_opts); - break; - case VK_SHADER_STAGE_GEOMETRY_BIT: - code = pgraph_glsl_gen_geom(&key->geom.state, key->geom.glsl_opts); - break; - case VK_SHADER_STAGE_FRAGMENT_BIT: - code = pgraph_glsl_gen_psh(&key->psh.state, key->psh.glsl_opts); - break; - default: - assert(!"Invalid shader module kind"); - code = NULL; - } - - ShaderModuleInfo *info = pgraph_vk_create_shader_module_from_glsl( - r, key->kind, mstring_get_str(code)); - pgraph_vk_ref_shader_module(info); - mstring_unref(code); - - target->module_info = info; - qatomic_set(&target->ready, true); -} - -static void process_pipeline_job(PGRAPHVkState *r, CompileJob *job) -{ - PipelineBinding *target = job->pipeline.target; - PipelineCreateParams *p = &job->pipeline.params; - - VkPipelineVertexInputStateCreateInfo vertex_input = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - .vertexBindingDescriptionCount = p->num_binding_descs, - .pVertexBindingDescriptions = p->binding_descs, - .vertexAttributeDescriptionCount = p->num_attr_descs, - .pVertexAttributeDescriptions = p->attr_descs, - }; - - VkPipelineInputAssemblyStateCreateInfo input_assembly = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, - .topology = p->topology, - .primitiveRestartEnable = VK_FALSE, - }; - - VkPipelineViewportStateCreateInfo viewport_state = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - .viewportCount = 1, - .scissorCount = 1, - }; - - VkPipelineMultisampleStateCreateInfo multisampling = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .sampleShadingEnable = VK_FALSE, - .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, - }; - - VkPipelineColorBlendStateCreateInfo color_blending = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, - .logicOpEnable = VK_FALSE, - .logicOp = VK_LOGIC_OP_COPY, - .attachmentCount = p->has_color ? 1 : 0, - .pAttachments = p->has_color ? &p->color_blend_attachment : NULL, - .blendConstants[0] = p->blend_constants[0], - .blendConstants[1] = p->blend_constants[1], - .blendConstants[2] = p->blend_constants[2], - .blendConstants[3] = p->blend_constants[3], - }; - - VkPipelineDynamicStateCreateInfo dynamic_state = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .dynamicStateCount = p->num_dynamic_states, - .pDynamicStates = p->dynamic_states, - }; - - VkGraphicsPipelineCreateInfo pipeline_create_info = { - .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - .stageCount = p->num_shader_stages, - .pStages = p->shader_stages, - .pVertexInputState = &vertex_input, - .pInputAssemblyState = &input_assembly, - .pViewportState = &viewport_state, - .pRasterizationState = &p->rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = p->has_zeta ? &p->depth_stencil : NULL, - .pColorBlendState = &color_blending, - .pDynamicState = &dynamic_state, - .layout = p->layout, - .renderPass = p->render_pass, - .subpass = 0, - .basePipelineHandle = VK_NULL_HANDLE, - }; - - VkPipeline pipeline = VK_NULL_HANDLE; - VkResult result = vkCreateGraphicsPipelines( - p->device, p->vk_pipeline_cache, 1, &pipeline_create_info, NULL, - &pipeline); - - if (result == VK_SUCCESS) { - target->pipeline = pipeline; - target->layout = p->layout; - target->render_pass = p->render_pass; - target->has_dynamic_line_width = p->has_dynamic_line_width; - } else if (p->layout != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(p->device, p->layout, NULL); - } - - for (int i = 0; i < p->num_module_infos; i++) { - if (p->module_infos[i]) { - pgraph_vk_unref_shader_module(r, p->module_infos[i]); - } - } - - qatomic_set(&target->pending, false); -} - -static void *compile_worker_func(void *opaque) -{ - PGRAPHVkState *r = opaque; - - while (true) { - qemu_mutex_lock(&r->compile_worker.lock); - - while (QSIMPLEQ_EMPTY(&r->compile_worker.queue) && - !r->compile_worker.shutdown) { - qemu_cond_wait(&r->compile_worker.cond, &r->compile_worker.lock); - } - - if (r->compile_worker.shutdown && - QSIMPLEQ_EMPTY(&r->compile_worker.queue)) { - qemu_mutex_unlock(&r->compile_worker.lock); - break; - } - - CompileJob *job = QSIMPLEQ_FIRST(&r->compile_worker.queue); - QSIMPLEQ_REMOVE_HEAD(&r->compile_worker.queue, entry); - r->compile_worker.queue_depth--; - - qemu_mutex_unlock(&r->compile_worker.lock); - - switch (job->type) { - case COMPILE_JOB_SHADER_MODULE: - process_shader_module_job(r, job); - break; - case COMPILE_JOB_PIPELINE: - process_pipeline_job(r, job); - break; - } - - g_free(job); - } - - return NULL; -} - -void pgraph_vk_compile_worker_enqueue(PGRAPHVkState *r, CompileJob *job) -{ - qemu_mutex_lock(&r->compile_worker.lock); - QSIMPLEQ_INSERT_TAIL(&r->compile_worker.queue, job, entry); - r->compile_worker.queue_depth++; - qemu_cond_signal(&r->compile_worker.cond); - qemu_mutex_unlock(&r->compile_worker.lock); -} - -void pgraph_vk_compile_worker_init(PGRAPHVkState *r) -{ - qemu_mutex_init(&r->compile_worker.lock); - qemu_cond_init(&r->compile_worker.cond); - QSIMPLEQ_INIT(&r->compile_worker.queue); - r->compile_worker.shutdown = false; - r->compile_worker.initialized = true; - r->compile_worker.queue_depth = 0; - qemu_thread_create(&r->compile_worker.thread, "pgraph.vk.compile", - compile_worker_func, r, QEMU_THREAD_JOINABLE); -} - -void pgraph_vk_compile_worker_shutdown(PGRAPHVkState *r) -{ - if (!r->compile_worker.initialized) { - return; - } - - qemu_mutex_lock(&r->compile_worker.lock); - r->compile_worker.shutdown = true; - qemu_cond_signal(&r->compile_worker.cond); - qemu_mutex_unlock(&r->compile_worker.lock); - - qemu_thread_join(&r->compile_worker.thread); - - CompileJob *job; - while ((job = QSIMPLEQ_FIRST(&r->compile_worker.queue)) != NULL) { - QSIMPLEQ_REMOVE_HEAD(&r->compile_worker.queue, entry); - if (job->type == COMPILE_JOB_PIPELINE) { - PipelineCreateParams *p = &job->pipeline.params; - if (p->layout != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(p->device, p->layout, NULL); - } - for (int i = 0; i < p->num_module_infos; i++) { - if (p->module_infos[i]) { - pgraph_vk_unref_shader_module(r, p->module_infos[i]); - } - } - } - g_free(job); - } - - qemu_mutex_destroy(&r->compile_worker.lock); - qemu_cond_destroy(&r->compile_worker.cond); - r->compile_worker.initialized = false; -} - -#endif diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c index b4c8e6fde9..440ec7ffd4 100644 --- a/hw/xbox/nv2a/pgraph/vk/draw.c +++ b/hw/xbox/nv2a/pgraph/vk/draw.c @@ -19,36 +19,14 @@ #include "qemu/osdep.h" #include "qemu/fast-hash.h" -#include "qemu/timer.h" #include "renderer.h" -#include "ui/xemu-settings.h" #include "hw/xbox/nv2a/pgraph/prim_rewrite.h" #include -#ifdef __aarch64__ -#include -#endif #ifdef __ANDROID__ #include #include #endif -#if OPT_REORDER_SAFE_WINDOWS -static bool g_xemu_draw_reorder = true; -#endif -#if OPT_ASYNC_COMPILE -static bool g_xemu_async_compile = true; - -void xemu_set_async_compile(bool enable) -{ - g_xemu_async_compile = enable; -} - -bool xemu_get_async_compile(void) -{ - return g_xemu_async_compile; -} -#endif - void pgraph_vk_draw_begin(NV2AState *d) { PGRAPHState *pg = &d->pgraph; @@ -66,21 +44,6 @@ void pgraph_vk_draw_begin(NV2AState *d) pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1) & NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE; bool is_nop_draw = !(color_write || depth_test || stencil_test); -#if OPT_REORDER_SAFE_WINDOWS - if (g_xemu_draw_reorder) { - PGRAPHVkState *r = pg->vk_renderer_state; - bool surface_shape_changed = - memcmp(&pg->surface_shape, &pg->last_surface_shape, - sizeof(SurfaceShape)) != 0; - bool framebuffer_will_change = - surface_shape_changed && - (pg->surface_shape.color_format || pg->surface_shape.zeta_format); - if (r->reorder_window.count > 0 && framebuffer_will_change) { - pgraph_vk_flush_reorder_window(d); - } - } -#endif - pgraph_vk_surface_update(d, true, true, depth_test || stencil_test); if (is_nop_draw) { @@ -115,19 +78,8 @@ static void pipeline_cache_entry_init(Lru *lru, LruNode *node, snode->layout = VK_NULL_HANDLE; snode->pipeline = VK_NULL_HANDLE; snode->draw_time = 0; -#if OPT_ASYNC_COMPILE - snode->pending = false; -#endif } -#if OPT_ASYNC_COMPILE -static bool pipeline_cache_pre_evict(Lru *lru, LruNode *node) -{ - PipelineBinding *snode = container_of(node, PipelineBinding, node); - return !snode->pending; -} -#endif - static void pipeline_cache_entry_post_evict(Lru *lru, LruNode *node) { PGRAPHVkState *r = container_of(lru, PGRAPHVkState, pipeline_cache); @@ -152,8 +104,6 @@ static bool pipeline_cache_entry_compare(Lru *lru, LruNode *node, } #ifdef __ANDROID__ -#define PIPELINE_CACHE_SAVE_INTERVAL_US (30 * 1000000LL) - static char *get_pipeline_cache_path(PGRAPHVkState *r) { char *pref_path = SDL_GetPrefPath("xemu", "xemu"); @@ -167,62 +117,6 @@ static char *get_pipeline_cache_path(PGRAPHVkState *r) SDL_free(pref_path); return path; } - -static void save_pipeline_cache_to_disk(PGRAPHVkState *r) -{ - g_autofree char *cache_path = get_pipeline_cache_path(r); - size_t data_size = 0; - VkResult res; - - if (!cache_path) { - return; - } - - res = vkGetPipelineCacheData(r->device, r->vk_pipeline_cache, - &data_size, NULL); - if (res != VK_SUCCESS || data_size == 0) { - return; - } - - g_autofree void *data = g_malloc(data_size); - res = vkGetPipelineCacheData(r->device, r->vk_pipeline_cache, - &data_size, data); - if (res != VK_SUCCESS) { - return; - } - - GError *err = NULL; - if (g_file_set_contents(cache_path, (const gchar *)data, - (gssize)data_size, &err)) { - __android_log_print(ANDROID_LOG_INFO, "xemu-vk", - "Saved pipeline cache: %zu bytes", data_size); - } else { - __android_log_print(ANDROID_LOG_WARN, "xemu-vk", - "Failed to save pipeline cache"); - if (err) { - g_error_free(err); - } - } -} - -static void maybe_save_pipeline_cache(PGRAPHVkState *r) -{ - int64_t now; - - if (!g_config.perf.cache_shaders) { - return; - } - - now = qemu_clock_get_us(QEMU_CLOCK_REALTIME); - if (r->pipeline_cache_last_save_us != 0 && - (now - r->pipeline_cache_last_save_us) < - PIPELINE_CACHE_SAVE_INTERVAL_US) { - return; - } - - r->pipeline_cache_last_save_us = now; - save_pipeline_cache_to_disk(r); -} #endif static void init_pipeline_cache(PGRAPHState *pg) @@ -238,39 +132,33 @@ static void init_pipeline_cache(PGRAPHState *pg) }; #ifdef __ANDROID__ - g_autofree gchar *cache_data = NULL; + g_autofree char *cache_path = get_pipeline_cache_path(r); + gchar *cache_data = NULL; gsize cache_data_size = 0; - if (g_config.perf.cache_shaders) { - g_autofree char *cache_path = get_pipeline_cache_path(r); - if (cache_path) { - GError *err = NULL; - if (g_file_get_contents(cache_path, &cache_data, &cache_data_size, - &err)) { - cache_info.initialDataSize = (size_t)cache_data_size; - cache_info.pInitialData = (const void *)cache_data; - __android_log_print(ANDROID_LOG_INFO, "xemu-vk", - "Loaded pipeline cache: %zu bytes", - cache_data_size); - } else { - if (err) { - g_error_free(err); - } + if (cache_path) { + GError *err = NULL; + if (g_file_get_contents(cache_path, &cache_data, &cache_data_size, &err)) { + cache_info.initialDataSize = (size_t)cache_data_size; + cache_info.pInitialData = (const void *)cache_data; + __android_log_print(ANDROID_LOG_INFO, "xemu-vk", + "Loaded pipeline cache: %zu bytes", cache_data_size); + } else { + if (err) { + g_error_free(err); } } } #endif - VkResult cache_result = vkCreatePipelineCache(r->device, &cache_info, NULL, - &r->vk_pipeline_cache); - if (cache_result != VK_SUCCESS) { - cache_info.initialDataSize = 0; - cache_info.pInitialData = NULL; - VK_CHECK(vkCreatePipelineCache(r->device, &cache_info, NULL, - &r->vk_pipeline_cache)); - } + VK_CHECK(vkCreatePipelineCache(r->device, &cache_info, NULL, + &r->vk_pipeline_cache)); + +#ifdef __ANDROID__ + g_free(cache_data); +#endif const size_t pipeline_cache_size = 2048; - lru_init(&r->pipeline_cache, 4096); + lru_init(&r->pipeline_cache, 1u << 16); r->pipeline_cache_entries = g_malloc_n(pipeline_cache_size, sizeof(PipelineBinding)); assert(r->pipeline_cache_entries != NULL); @@ -281,26 +169,44 @@ static void init_pipeline_cache(PGRAPHState *pg) r->pipeline_cache.init_node = pipeline_cache_entry_init; r->pipeline_cache.compare_nodes = pipeline_cache_entry_compare; r->pipeline_cache.post_node_evict = pipeline_cache_entry_post_evict; -#if OPT_ASYNC_COMPILE - r->pipeline_cache.pre_node_evict = pipeline_cache_pre_evict; -#endif } static void finalize_pipeline_cache(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; -#ifdef __ANDROID__ - if (g_config.perf.cache_shaders) { - save_pipeline_cache_to_disk(r); - } -#endif - lru_flush(&r->pipeline_cache); - lru_destroy(&r->pipeline_cache); g_free(r->pipeline_cache_entries); r->pipeline_cache_entries = NULL; +#ifdef __ANDROID__ + g_autofree char *cache_path = get_pipeline_cache_path(r); + if (cache_path) { + size_t data_size = 0; + VkResult res = vkGetPipelineCacheData(r->device, r->vk_pipeline_cache, + &data_size, NULL); + if (res == VK_SUCCESS && data_size > 0) { + g_autofree void *data = g_malloc(data_size); + res = vkGetPipelineCacheData(r->device, r->vk_pipeline_cache, + &data_size, data); + if (res == VK_SUCCESS) { + GError *err = NULL; + if (g_file_set_contents(cache_path, (const gchar *)data, + (gssize)data_size, &err)) { + __android_log_print(ANDROID_LOG_INFO, "xemu-vk", + "Saved pipeline cache: %zu bytes", data_size); + } else { + __android_log_print(ANDROID_LOG_WARN, "xemu-vk", + "Failed to save pipeline cache"); + if (err) { + g_error_free(err); + } + } + } + } + } +#endif + vkDestroyPipelineCache(r->device, r->vk_pipeline_cache, NULL); } @@ -378,10 +284,6 @@ void pgraph_vk_finalize_pipelines(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; -#if OPT_ASYNC_COMPILE - pgraph_vk_compile_worker_shutdown(r); -#endif - finalize_clear_shaders(pg); finalize_pipeline_cache(pg); finalize_render_passes(r); @@ -527,26 +429,6 @@ static void create_frame_buffer(PGRAPHState *pg) assert(r->color_binding || r->zeta_binding); - SurfaceBinding *binding = r->color_binding ? : r->zeta_binding; - VkImageView color_view = r->color_binding ? r->color_binding->image_view - : VK_NULL_HANDLE; - VkImageView zeta_view = r->zeta_binding ? r->zeta_binding->image_view - : VK_NULL_HANDLE; - uint32_t width = binding->width; - uint32_t height = binding->height; - pgraph_apply_scaling_factor(pg, &width, &height); - - for (int i = 0; i < r->fb_cache_count; i++) { - if (r->fb_cache[i].render_pass == r->render_pass && - r->fb_cache[i].color_view == color_view && - r->fb_cache[i].zeta_view == zeta_view && - r->fb_cache[i].width == width && - r->fb_cache[i].height == height) { - r->current_framebuffer = r->fb_cache[i].framebuffer; - return; - } - } - if (r->framebuffer_index >= ARRAY_SIZE(r->framebuffers)) { pgraph_vk_finish(pg, VK_FINISH_REASON_NEED_BUFFER_SPACE); } @@ -555,35 +437,26 @@ static void create_frame_buffer(PGRAPHState *pg) int attachment_count = 0; if (r->color_binding) { - attachments[attachment_count++] = color_view; + attachments[attachment_count++] = r->color_binding->image_view; } if (r->zeta_binding) { - attachments[attachment_count++] = zeta_view; + attachments[attachment_count++] = r->zeta_binding->image_view; } + SurfaceBinding *binding = r->color_binding ? : r->zeta_binding; + VkFramebufferCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = r->render_pass, .attachmentCount = attachment_count, .pAttachments = attachments, - .width = width, - .height = height, + .width = binding->width, + .height = binding->height, .layers = 1, }; + pgraph_apply_scaling_factor(pg, &create_info.width, &create_info.height); VK_CHECK(vkCreateFramebuffer(r->device, &create_info, NULL, &r->framebuffers[r->framebuffer_index++])); - r->current_framebuffer = r->framebuffers[r->framebuffer_index - 1]; - - if (r->fb_cache_count < FB_CACHE_MAX) { - r->fb_cache[r->fb_cache_count++] = (typeof(r->fb_cache[0])) { - .render_pass = r->render_pass, - .color_view = color_view, - .zeta_view = zeta_view, - .width = width, - .height = height, - .framebuffer = r->current_framebuffer, - }; - } } static void destroy_framebuffers(PGRAPHState *pg) @@ -596,8 +469,6 @@ static void destroy_framebuffers(PGRAPHState *pg) r->framebuffers[i] = VK_NULL_HANDLE; } r->framebuffer_index = 0; - r->fb_cache_count = 0; - r->current_framebuffer = VK_NULL_HANDLE; } static void create_clear_pipeline(PGRAPHState *pg) @@ -787,9 +658,6 @@ static void create_clear_pipeline(PGRAPHState *pg) r->pipeline_binding = snode; r->pipeline_binding_changed = true; -#ifdef __ANDROID__ - maybe_save_pipeline_cache(r); -#endif NV2A_VK_DGROUP_END(); } @@ -810,17 +678,21 @@ static bool check_pipeline_dirty(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - if (!r->pipeline_binding || -#if OPT_ASYNC_COMPILE - r->pipeline_binding->pending || -#endif - r->pipeline_binding->pipeline == VK_NULL_HANDLE || - r->shader_bindings_changed || + if (!r->pipeline_binding || r->shader_bindings_changed || r->texture_bindings_changed || check_render_pass_dirty(pg)) { return true; } - if (pg->pipeline_state_gen != r->last_pipeline_state_gen) { - return true; + + const unsigned int regs[] = { + NV_PGRAPH_BLEND, NV_PGRAPH_BLENDCOLOR, NV_PGRAPH_CONTROL_0, + NV_PGRAPH_CONTROL_1, NV_PGRAPH_CONTROL_2, NV_PGRAPH_CONTROL_3, + NV_PGRAPH_SETUPRASTER, NV_PGRAPH_ZOFFSETBIAS, NV_PGRAPH_ZOFFSETFACTOR, + }; + + for (int i = 0; i < ARRAY_SIZE(regs); i++) { + if (pgraph_is_reg_dirty(pg, regs[i])) { + return true; + } } // FIXME: Use dirty bits instead @@ -873,35 +745,16 @@ static void create_pipeline(PGRAPHState *pg) NV2AState *d = container_of(pg, NV2AState, pgraph); PGRAPHVkState *r = pg->vk_renderer_state; - bool textures_clean = pgraph_vk_check_textures_fast_skip(pg); - if (r->pipeline_binding && -#if OPT_ASYNC_COMPILE - !r->pipeline_binding->pending && -#endif - r->pipeline_binding->pipeline != VK_NULL_HANDLE && - pg->texture_state_gen == r->last_texture_state_gen && - textures_clean && - !check_render_pass_dirty(pg) && - pg->shader_state_gen == r->last_shader_state_gen && - pg->pipeline_state_gen == r->last_pipeline_state_gen && - pg->primitive_mode == r->shader_binding->state.geom.primitive_mode) { - NV2A_VK_DGROUP_END(); - return; - } - - if (pg->texture_state_gen != r->last_texture_state_gen || !textures_clean) { - pgraph_vk_bind_textures(d); - r->last_texture_state_gen = pg->texture_state_gen; - } + pgraph_vk_bind_textures(d); pgraph_vk_bind_shaders(pg); + // FIXME: If nothing was dirty, don't even try creating the key or hashing. + // Just use the same pipeline. bool pipeline_dirty = check_pipeline_dirty(pg); pgraph_clear_dirty_reg_map(pg); - r->last_pipeline_state_gen = pg->pipeline_state_gen; - r->last_shader_state_gen = pg->shader_state_gen; - r->last_any_reg_gen = pg->any_reg_gen; + // FIXME: We could clear less if (r->pipeline_binding && !pipeline_dirty) { NV2A_VK_DPRINTF("Cache hit"); @@ -915,18 +768,6 @@ static void create_pipeline(PGRAPHState *pg) LruNode *node = lru_lookup(&r->pipeline_cache, hash, &key); PipelineBinding *snode = container_of(node, PipelineBinding, node); - memcpy(&snode->key, &key, sizeof(key)); - -#if OPT_ASYNC_COMPILE - if (snode->pending) { - NV2A_VK_DPRINTF("Pending pipeline"); - r->pipeline_binding_changed = r->pipeline_binding != snode; - r->pipeline_binding = snode; - NV2A_VK_DGROUP_END(); - return; - } -#endif - if (snode->pipeline != VK_NULL_HANDLE) { NV2A_VK_DPRINTF("Cache hit"); r->pipeline_binding_changed = r->pipeline_binding != snode; @@ -938,15 +779,7 @@ static void create_pipeline(PGRAPHState *pg) NV2A_VK_DPRINTF("Cache miss"); nv2a_profile_inc_counter(NV2A_PROF_PIPELINE_GEN); -#if OPT_ASYNC_COMPILE - if (xemu_get_async_compile() && - (!r->shader_binding || !qatomic_read(&r->shader_binding->ready))) { - r->pipeline_binding_changed = r->pipeline_binding != snode; - r->pipeline_binding = snode; - NV2A_VK_DGROUP_END(); - return; - } -#endif + memcpy(&snode->key, &key, sizeof(key)); uint32_t control_0 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0); bool depth_test = control_0 & NV_PGRAPH_CONTROL_0_ZENABLE; @@ -1193,62 +1026,28 @@ static void create_pipeline(PGRAPHState *pg) // } - VkPushConstantRange push_constant_ranges[2]; - int num_push_constant_ranges = 0; VkPipelineLayoutCreateInfo pipeline_layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &r->descriptor_set_layout, }; -#if OPT_BINDLESS_TEXTURES - VkDescriptorSetLayout set_layouts[2]; - if (r->bindless_textures_supported) { - set_layouts[0] = r->bindless_set_layout; - set_layouts[1] = r->descriptor_set_layout; - pipeline_layout_info.setLayoutCount = 2; - pipeline_layout_info.pSetLayouts = set_layouts; - push_constant_ranges[num_push_constant_ranges++] = - (VkPushConstantRange){ - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - .offset = r->tex_push_offset, - .size = NV2A_MAX_TEXTURES * sizeof(uint32_t), - }; - } -#endif - + VkPushConstantRange push_constant_range; if (r->use_push_constants_for_uniform_attrs) { int num_uniform_attributes = __builtin_popcount(r->shader_binding->state.vsh.uniform_attrs); -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported && - num_uniform_attributes > r->max_vertex_push_attrs) { - num_uniform_attributes = 0; - } -#endif if (num_uniform_attributes) { -#if OPT_BINDLESS_TEXTURES - uint32_t vertex_push_offset = - r->bindless_textures_supported && r->tex_push_offset == 0 - ? NV2A_MAX_TEXTURES * sizeof(uint32_t) - : 0; -#else - uint32_t vertex_push_offset = 0; -#endif - push_constant_ranges[num_push_constant_ranges++] = - (VkPushConstantRange){ - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, - .offset = vertex_push_offset, - .size = num_uniform_attributes * 4 * sizeof(float), - }; + push_constant_range = (VkPushConstantRange){ + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, + // FIXME: Minimize push constants + .size = num_uniform_attributes * 4 * sizeof(float), + }; + pipeline_layout_info.pushConstantRangeCount = 1; + pipeline_layout_info.pPushConstantRanges = &push_constant_range; } } - if (num_push_constant_ranges > 0) { - pipeline_layout_info.pushConstantRangeCount = num_push_constant_ranges; - pipeline_layout_info.pPushConstantRanges = push_constant_ranges; - } - VkPipelineLayout layout; VK_CHECK(vkCreatePipelineLayout(r->device, &pipeline_layout_info, NULL, &layout)); @@ -1270,62 +1069,6 @@ static void create_pipeline(PGRAPHState *pg) .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, }; - -#if OPT_ASYNC_COMPILE - if (xemu_get_async_compile()) { - CompileJob *job = g_malloc0(sizeof(*job)); - PipelineCreateParams *p = &job->pipeline.params; - - job->type = COMPILE_JOB_PIPELINE; - job->pipeline.target = snode; - p->device = r->device; - p->vk_pipeline_cache = r->vk_pipeline_cache; - memcpy(p->shader_stages, shader_stages, - num_active_shader_stages * sizeof(shader_stages[0])); - p->num_shader_stages = num_active_shader_stages; - p->num_module_infos = 0; - p->module_infos[p->num_module_infos] = r->shader_binding->vsh.module_info; - pgraph_vk_ref_shader_module(p->module_infos[p->num_module_infos++]); - if (r->shader_binding->geom.module_info) { - p->module_infos[p->num_module_infos] = - r->shader_binding->geom.module_info; - pgraph_vk_ref_shader_module(p->module_infos[p->num_module_infos++]); - } - p->module_infos[p->num_module_infos] = r->shader_binding->psh.module_info; - pgraph_vk_ref_shader_module(p->module_infos[p->num_module_infos++]); - memcpy(p->binding_descs, r->vertex_binding_descriptions, - r->num_active_vertex_binding_descriptions * - sizeof(VkVertexInputBindingDescription)); - memcpy(p->attr_descs, r->vertex_attribute_descriptions, - r->num_active_vertex_attribute_descriptions * - sizeof(VkVertexInputAttributeDescription)); - p->num_binding_descs = r->num_active_vertex_binding_descriptions; - p->num_attr_descs = r->num_active_vertex_attribute_descriptions; - p->topology = input_assembly.topology; - p->rasterizer = rasterizer; - p->depth_stencil = depth_stencil; - p->has_zeta = r->zeta_binding != NULL; - p->color_blend_attachment = color_blend_attachment; - p->has_color = r->color_binding != NULL; - memcpy(p->blend_constants, color_blending.blendConstants, - sizeof(p->blend_constants)); - memcpy(p->dynamic_states, dynamic_states, - num_dynamic_states * sizeof(dynamic_states[0])); - p->num_dynamic_states = num_dynamic_states; - p->has_dynamic_line_width = snode->has_dynamic_line_width; - p->layout = layout; - p->render_pass = pipeline_create_info.renderPass; - - snode->draw_time = pg->draw_time; - snode->pending = true; - r->pipeline_binding = snode; - r->pipeline_binding_changed = true; - pgraph_vk_compile_worker_enqueue(r, job); - NV2A_VK_DGROUP_END(); - return; - } -#endif - VkPipeline pipeline; VK_CHECK(vkCreateGraphicsPipelines(r->device, r->vk_pipeline_cache, 1, &pipeline_create_info, NULL, &pipeline)); @@ -1338,32 +1081,14 @@ static void create_pipeline(PGRAPHState *pg) r->pipeline_binding = snode; r->pipeline_binding_changed = true; -#ifdef __ANDROID__ - maybe_save_pipeline_cache(r); -#endif NV2A_VK_DGROUP_END(); } -static bool can_push_vertex_attr_values(PGRAPHVkState *r) -{ - if (!r->use_push_constants_for_uniform_attrs) { - return false; - } -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported && - __builtin_popcount(r->shader_binding->state.vsh.uniform_attrs) > - r->max_vertex_push_attrs) { - return false; - } -#endif - return true; -} - static void push_vertex_attr_values(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - if (!can_push_vertex_attr_values(r)) { + if (!r->use_push_constants_for_uniform_attrs) { return; } @@ -1376,16 +1101,8 @@ static void push_vertex_attr_values(PGRAPHState *pg) values, &num_uniform_attrs); if (num_uniform_attrs > 0) { -#if OPT_BINDLESS_TEXTURES - uint32_t vertex_push_offset = - r->bindless_textures_supported && r->tex_push_offset == 0 - ? NV2A_MAX_TEXTURES * sizeof(uint32_t) - : 0; -#else - uint32_t vertex_push_offset = 0; -#endif vkCmdPushConstants(r->command_buffer, r->pipeline_binding->layout, - VK_SHADER_STAGE_VERTEX_BIT, vertex_push_offset, + VK_SHADER_STAGE_VERTEX_BIT, 0, num_uniform_attrs * 4 * sizeof(float), &values); } @@ -1397,46 +1114,11 @@ static void bind_descriptor_sets(PGRAPHState *pg) assert(r->descriptor_set_index >= 1); vkCmdBindDescriptorSets(r->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - r->pipeline_binding->layout, -#if OPT_BINDLESS_TEXTURES - r->bindless_textures_supported ? 1 : 0, -#else - 0, -#endif - 1, + r->pipeline_binding->layout, 0, 1, &r->descriptor_sets[r->descriptor_set_index - 1], 0, NULL); } -#if OPT_BINDLESS_TEXTURES -static void bind_bindless_set(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - if (!r->bindless_textures_supported) { - return; - } - - vkCmdBindDescriptorSets(r->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - r->pipeline_binding->layout, 0, 1, - &r->bindless_descriptor_set, 0, NULL); -} - -static void push_texture_indices(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - if (!r->bindless_textures_supported) { - return; - } - - vkCmdPushConstants(r->command_buffer, r->pipeline_binding->layout, - VK_SHADER_STAGE_FRAGMENT_BIT, r->tex_push_offset, - sizeof(r->tex_bindless_indices), - r->tex_bindless_indices); -} -#endif - static void begin_query(PGRAPHVkState *r) { assert(r->in_command_buffer); @@ -1491,10 +1173,8 @@ static void sync_staging_buffer(PGRAPHState *pg, VkCommandBuffer cmd, switch (index_dst) { case BUFFER_INDEX: - dst_access_mask = VK_ACCESS_INDEX_READ_BIT | - VK_ACCESS_INDIRECT_COMMAND_READ_BIT; - dst_stage_mask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | - VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; + dst_access_mask = VK_ACCESS_INDEX_READ_BIT; + dst_stage_mask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; break; case BUFFER_VERTEX_INLINE: dst_access_mask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; @@ -1561,42 +1241,12 @@ static void begin_render_pass(PGRAPHState *pg) vp_height = pg->surface_binding_dim.height; pgraph_apply_scaling_factor(pg, &vp_width, &vp_height); - assert(r->current_framebuffer != VK_NULL_HANDLE); - - if (r->zeta_binding && - r->zeta_binding->image_layout != - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { - VkImageMemoryBarrier barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .oldLayout = r->zeta_binding->image_layout, - .newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = r->zeta_binding->image, - .subresourceRange = { - .aspectMask = r->zeta_binding->host_fmt.aspect, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, - .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - }; - vkCmdPipelineBarrier( - r->command_buffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - 0, 0, NULL, 0, NULL, 1, &barrier); - r->zeta_binding->image_layout = - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - } + assert(r->framebuffer_index > 0); VkRenderPassBeginInfo render_pass_begin_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = r->render_pass, - .framebuffer = r->current_framebuffer, + .framebuffer = r->framebuffers[r->framebuffer_index - 1], .renderArea.extent.width = vp_width, .renderArea.extent.height = vp_height, .clearValueCount = 0, @@ -1616,32 +1266,6 @@ static void end_render_pass(PGRAPHVkState *r) } } -#if OPT_REORDER_SAFE_WINDOWS -static void flush_reorder_window_internal(NV2AState *d); -static bool classify_draw_safe(PGRAPHState *pg); -static bool try_snapshot_draw_arrays(NV2AState *d, ReorderWindowEntry *entry); -static bool try_snapshot_inline_elements(NV2AState *d, - ReorderWindowEntry *entry); -#endif - -typedef struct VertexBufferRemap { - uint16_t attributes; - size_t buffer_space_required; - struct { - VkDeviceAddress offset; - VkDeviceSize old_stride; - VkDeviceSize new_stride; - } map[NV2A_VERTEXSHADER_ATTRIBUTES]; -} VertexBufferRemap; - -static bool ensure_buffer_space(PGRAPHState *pg, int index, VkDeviceSize size); -static VertexBufferRemap remap_unaligned_attributes(PGRAPHState *pg, - uint32_t num_vertices); -static void copy_remapped_attributes_to_inline_buffer(PGRAPHState *pg, - VertexBufferRemap remap, - uint32_t start_vertex, - uint32_t num_vertices); - const enum NV2A_PROF_COUNTERS_ENUM finish_reason_to_counter_enum[] = { [VK_FINISH_REASON_VERTEX_BUFFER_DIRTY] = NV2A_PROF_FINISH_VERTEX_BUFFER_DIRTY, [VK_FINISH_REASON_SURFACE_CREATE] = NV2A_PROF_FINISH_SURFACE_CREATE, @@ -1658,14 +1282,6 @@ void pgraph_vk_finish(PGRAPHState *pg, FinishReason finish_reason) { PGRAPHVkState *r = pg->vk_renderer_state; -#if OPT_REORDER_SAFE_WINDOWS - if (r->reorder_window.count > 0) { - NV2AState *d = container_of(pg, NV2AState, pgraph); - flush_reorder_window_internal(d); - } - r->reorder_window.active = false; -#endif - assert(!r->in_draw); assert(r->debug_depth == 0); @@ -1760,9 +1376,6 @@ void pgraph_vk_begin_command_buffer(PGRAPHState *pg) &command_buffer_begin_info)); r->command_buffer_start_time = pg->draw_time; r->in_command_buffer = true; -#if OPT_BINDLESS_TEXTURES - r->bindless_set_bound = false; -#endif } // FIXME: Refactor below @@ -1810,67 +1423,17 @@ void pgraph_vk_end_nondraw_commands(PGRAPHState *pg, VkCommandBuffer cmd) static void begin_pre_draw(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - bool textures_clean = pgraph_vk_check_textures_fast_skip(pg); - -#if OPT_ASYNC_COMPILE - r->async_draw_skip = false; -#endif assert(r->color_binding || r->zeta_binding); assert(!r->color_binding || r->color_binding->initialized); assert(!r->zeta_binding || r->zeta_binding->initialized); - if (!pg->clearing && - r->pipeline_binding && - r->shader_binding && - r->pipeline_binding->pipeline != VK_NULL_HANDLE && - !r->pipeline_binding_changed && - r->in_command_buffer && - r->in_render_pass && - r->framebuffer_index > 0 && - !r->framebuffer_dirty && - !r->uniforms_changed && - r->descriptor_set_index > 0 && - pg->texture_state_gen == r->last_texture_state_gen && - textures_clean && - pg->shader_state_gen == r->last_shader_state_gen && - pg->pipeline_state_gen == r->last_pipeline_state_gen && - pg->any_reg_gen == r->last_any_reg_gen && - pg->primitive_mode == r->shader_binding->state.geom.primitive_mode && - !pg->program_data_dirty && - pg->vertex_attr_gen == r->pipeline_vertex_attr_gen) { - r->pre_draw_skipped = true; - return; - } - - r->pre_draw_skipped = false; - if (pg->clearing) { create_clear_pipeline(pg); } else { create_pipeline(pg); - r->pipeline_vertex_attr_gen = pg->vertex_attr_gen; } -#if OPT_ASYNC_COMPILE - if (!pg->clearing && xemu_get_async_compile() && r->pipeline_binding) { - bool skip = false; - if (!r->shader_binding || !qatomic_read(&r->shader_binding->ready)) { - skip = true; - } else if (r->pipeline_binding->pending) { - skip = true; - } else if (r->pipeline_binding->pipeline == VK_NULL_HANDLE) { - skip = true; - } - if (skip) { - r->async_draw_skip = true; - r->pre_draw_skipped = true; - pgraph_vk_ensure_command_buffer(pg); - return; - } - } -#endif - bool render_pass_dirty = r->pipeline_binding->render_pass != r->render_pass; if (r->framebuffer_dirty || render_pass_dirty) { @@ -1885,18 +1448,12 @@ static void begin_pre_draw(PGRAPHState *pg) } if (!pg->clearing) { pgraph_vk_update_descriptor_sets(pg); - r->shader_bindings_changed = false; - r->texture_bindings_changed = false; } if (r->framebuffer_index == 0) { create_frame_buffer(pg); } pgraph_vk_ensure_command_buffer(pg); - pgraph_clear_dirty_reg_map(pg); - r->last_any_reg_gen = pg->any_reg_gen; - r->last_shader_state_gen = pg->shader_state_gen; - r->last_pipeline_state_gen = pg->pipeline_state_gen; } static float clamp_line_width_to_device_limits(PGRAPHState *pg, float width) @@ -1950,7 +1507,6 @@ static void begin_draw(PGRAPHState *pg) nv2a_profile_inc_counter(NV2A_PROF_PIPELINE_BIND); vkCmdBindPipeline(r->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, r->pipeline_binding->pipeline); - r->pipeline_binding_changed = false; r->pipeline_binding->draw_time = pg->draw_time; unsigned int vp_width = pg->surface_binding_dim.width, @@ -1994,18 +1550,9 @@ static void begin_draw(PGRAPHState *pg) } } - if (!pg->clearing && !r->pre_draw_skipped) { -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported && !r->bindless_set_bound) { - bind_bindless_set(pg); - r->bindless_set_bound = true; - } -#endif + if (!pg->clearing) { bind_descriptor_sets(pg); push_vertex_attr_values(pg); -#if OPT_BINDLESS_TEXTURES - push_texture_indices(pg); -#endif } r->in_draw = true; @@ -2053,63 +1600,6 @@ void pgraph_vk_draw_end(NV2AState *d) return; } -#if OPT_REORDER_SAFE_WINDOWS - if (g_xemu_draw_reorder && - (pg->draw_arrays_length || pg->inline_elements_length) && - !pg->clearing) { - ReorderWindow *window = &r->reorder_window; - bool is_safe = classify_draw_safe(pg); - - if (is_safe && r->framebuffer_dirty) { - is_safe = false; - } - if (is_safe && pg->zpass_pixel_count_enable) { - is_safe = false; - } - - if (is_safe && window->count < REORDER_WINDOW_MAX) { - ReorderWindowEntry *entry = &window->entries[window->count]; - bool ok = pg->draw_arrays_length ? - try_snapshot_draw_arrays(d, entry) : - try_snapshot_inline_elements(d, entry); - if (ok) { - entry->sequence_number = window->count; - - int group = -1; - for (int i = 0; i < window->num_seen_pipelines; i++) { - if (window->seen_pipelines[i] == entry->pipeline_binding) { - group = window->seen_pipeline_group[i]; - break; - } - } - if (group < 0) { - if (window->num_seen_pipelines < REORDER_MAX_PIPELINES) { - window->seen_pipelines[window->num_seen_pipelines] = - entry->pipeline_binding; - window->seen_pipeline_group[window->num_seen_pipelines] = - window->next_group; - window->num_seen_pipelines++; - } - group = window->next_group++; - } - entry->group_order = group; - - window->count++; - window->active = true; - return; - } - } - - if (window->count > 0) { - flush_reorder_window_internal(d); - } - window->active = false; - } else if (r->reorder_window.count > 0) { - flush_reorder_window_internal(d); - r->reorder_window.active = false; - } -#endif - pgraph_vk_flush_draw(d); pg->draw_time++; @@ -2222,10 +1712,6 @@ void pgraph_vk_clear_surface(NV2AState *d, uint32_t parameter) PGRAPHState *pg = &d->pgraph; PGRAPHVkState *r = pg->vk_renderer_state; -#if OPT_REORDER_SAFE_WINDOWS - pgraph_vk_flush_reorder_window(d); -#endif - nv2a_profile_inc_counter(NV2A_PROF_CLEAR); bool write_color = (parameter & NV097_CLEAR_SURFACE_COLOR); @@ -2410,549 +1896,6 @@ static void bind_inline_vertex_buffer(PGRAPHState *pg, VkDeviceSize offset) bind_vertex_buffer(pg, 0xffff, offset); } -#if OPT_REORDER_SAFE_WINDOWS -static bool check_rt_as_texture_hazard(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { - TextureBinding *binding = r->texture_bindings[i]; - if (!binding || binding == &r->dummy_texture) { - continue; - } - - hwaddr tex_start = binding->key.texture_vram_offset; - hwaddr tex_end = tex_start + binding->key.texture_length; - - if (r->color_binding) { - hwaddr rt_start = r->color_binding->vram_addr; - hwaddr rt_end = rt_start + r->color_binding->size; - if (tex_start < rt_end && rt_start < tex_end) { - return true; - } - } - - if (r->zeta_binding) { - hwaddr zt_start = r->zeta_binding->vram_addr; - hwaddr zt_end = zt_start + r->zeta_binding->size; - if (tex_start < zt_end && zt_start < tex_end) { - return true; - } - } - } - - return false; -} - -static bool classify_draw_safe(PGRAPHState *pg) -{ - uint32_t control_0 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0); - uint32_t blend = pgraph_reg_r(pg, NV_PGRAPH_BLEND); - uint32_t control_1 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1); - - if (blend & NV_PGRAPH_BLEND_EN) { - uint32_t src = GET_MASK(blend, NV_PGRAPH_BLEND_SFACTOR); - uint32_t dst = GET_MASK(blend, NV_PGRAPH_BLEND_DFACTOR); - if (src != NV_PGRAPH_BLEND_SFACTOR_ONE || - dst != NV_PGRAPH_BLEND_DFACTOR_ZERO) { - return false; - } - } - - if ((control_0 & (NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE)) != - (NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE | - NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE)) { - return false; - } - - if (!(control_0 & NV_PGRAPH_CONTROL_0_ZENABLE) || - !(control_0 & NV_PGRAPH_CONTROL_0_ZWRITEENABLE)) { - return false; - } - - uint32_t zfunc = GET_MASK(control_0, NV_PGRAPH_CONTROL_0_ZFUNC); - if (zfunc != NV_PGRAPH_CONTROL_0_ZFUNC_LESS && - zfunc != NV_PGRAPH_CONTROL_0_ZFUNC_LEQUAL) { - return false; - } - - if (control_1 & NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE) { - return false; - } - - if (control_0 & NV_PGRAPH_CONTROL_0_ALPHATESTENABLE) { - uint32_t afunc = GET_MASK(control_0, NV_PGRAPH_CONTROL_0_ALPHAFUNC); - uint32_t aref = GET_MASK(control_0, NV_PGRAPH_CONTROL_0_ALPHAREF); - bool safe_alpha = (afunc == ALPHA_FUNC_ALWAYS) || - (afunc == ALPHA_FUNC_GREATER && aref == 0); - if (!safe_alpha) { - return false; - } - } - - if (check_rt_as_texture_hazard(pg)) { - return false; - } - - return true; -} - -static bool reorder_uses_bindless_direct_surface_textures(PGRAPHVkState *r) -{ -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { - if (r->tex_surface_direct[i]) { - return true; - } - } - } -#endif - return false; -} - -static void snapshot_vertex_buffers(PGRAPHState *pg, ReorderWindowEntry *entry, - uint16_t inline_map, VkDeviceSize offset) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - entry->num_vertex_bindings = r->num_active_vertex_binding_descriptions; - for (int i = 0; i < entry->num_vertex_bindings; i++) { - int attr_idx = r->vertex_attribute_descriptions[i].location; - int buffer_idx = (inline_map & (1 << attr_idx)) ? BUFFER_VERTEX_INLINE : - BUFFER_VERTEX_RAM; - entry->vertex_buffers[i] = r->storage_buffers[buffer_idx].buffer; - entry->vertex_offsets[i] = - offset + r->vertex_attribute_offsets[attr_idx]; - } -} - -static void snapshot_draw_state(PGRAPHState *pg, ReorderWindowEntry *entry) -{ - unsigned int vp_width = pg->surface_binding_dim.width; - unsigned int vp_height = pg->surface_binding_dim.height; - pgraph_apply_scaling_factor(pg, &vp_width, &vp_height); - entry->viewport = (VkViewport){ - .width = vp_width, - .height = vp_height, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - - unsigned int xmin = pg->surface_shape.clip_x; - unsigned int ymin = pg->surface_shape.clip_y; - unsigned int scissor_width = pg->surface_shape.clip_width; - unsigned int scissor_height = pg->surface_shape.clip_height; - - pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); - pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); - pgraph_apply_scaling_factor(pg, &xmin, &ymin); - pgraph_apply_scaling_factor(pg, &scissor_width, &scissor_height); - - entry->scissor = (VkRect2D){ - .offset = { .x = xmin, .y = ymin }, - .extent = { .width = scissor_width, .height = scissor_height }, - }; -} - -static void snapshot_push_constants(PGRAPHState *pg, ReorderWindowEntry *entry) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - entry->use_push_constants = can_push_vertex_attr_values(r); - entry->num_push_values = 0; - - if (!entry->use_push_constants || !r->shader_binding) { - return; - } - - float values[NV2A_VERTEXSHADER_ATTRIBUTES][4]; - int num_uniform_attrs = 0; - pgraph_get_inline_values(pg, r->shader_binding->state.vsh.uniform_attrs, - values, &num_uniform_attrs); - entry->num_push_values = num_uniform_attrs; - if (num_uniform_attrs > 0) { - memcpy(entry->push_values, values, - num_uniform_attrs * 4 * sizeof(float)); - } -} - -static void snapshot_texture_indices(PGRAPHState *pg, ReorderWindowEntry *entry) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - entry->use_bindless_textures = false; -#if OPT_BINDLESS_TEXTURES - entry->use_bindless_textures = r->bindless_textures_supported; - if (entry->use_bindless_textures) { - memcpy(entry->tex_bindless_indices, r->tex_bindless_indices, - sizeof(entry->tex_bindless_indices)); - } -#endif -} - -static bool try_snapshot_draw_arrays(NV2AState *d, ReorderWindowEntry *entry) -{ - PGRAPHState *pg = &d->pgraph; - PGRAPHVkState *r = pg->vk_renderer_state; - - if (!(r->color_binding || r->zeta_binding)) { - return false; - } - - nv2a_profile_inc_counter(NV2A_PROF_DRAW_ARRAYS); - r->num_vertex_ram_buffer_syncs = 0; - - PrimAssemblyState assembly = { - .primitive_mode = pg->primitive_mode, - .polygon_mode = (enum ShaderPolygonMode)GET_MASK( - pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER), - NV_PGRAPH_SETUPRASTER_FRONTFACEMODE), - .last_provoking = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), - NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX) == - NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_LAST, - .flat_shading = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), - NV_PGRAPH_CONTROL_3_SHADEMODE) == - NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT, - }; - - pgraph_vk_bind_vertex_attributes(d, pg->draw_arrays_min_start, - pg->draw_arrays_max_count - 1, false, 0, - pg->draw_arrays_max_count - 1); - - uint32_t max_element = 0; - for (int i = 0; i < pg->draw_arrays_length; i++) { - max_element = MAX(max_element, pg->draw_arrays_start[i] + - pg->draw_arrays_count[i]); - } - - sync_vertex_ram_buffer(pg); - VertexBufferRemap remap = remap_unaligned_attributes(pg, max_element); - - PrimRewrite prim_rw = pgraph_prim_rewrite_ranges( - &r->prim_rewrite_buf, assembly, pg->draw_arrays_start, - pg->draw_arrays_count, pg->draw_arrays_length); - - if (prim_rw.num_indices > 0) { - ensure_buffer_space(pg, BUFFER_INDEX_STAGING, - prim_rw.num_indices * sizeof(uint32_t)); - } else if (pg->draw_arrays_length > 1) { - ensure_buffer_space(pg, BUFFER_INDEX_STAGING, - pg->draw_arrays_length * - sizeof(VkDrawIndirectCommand)); - } - - begin_pre_draw(pg); - if (r->async_draw_skip || - reorder_uses_bindless_direct_surface_textures(r) || - !r->pipeline_binding || r->descriptor_set_index == 0) { - return false; - } - - copy_remapped_attributes_to_inline_buffer(pg, remap, 0, max_element); - - entry->pipeline_binding = r->pipeline_binding; - entry->layout = r->pipeline_binding->layout; - entry->descriptor_set = r->descriptor_sets[r->descriptor_set_index - 1]; - entry->has_dynamic_line_width = r->pipeline_binding->has_dynamic_line_width; - if (entry->has_dynamic_line_width) { - entry->line_width = - clamp_line_width_to_device_limits(pg, pg->surface_scale_factor); - } - - snapshot_vertex_buffers(pg, entry, remap.attributes, 0); - snapshot_draw_state(pg, entry); - snapshot_push_constants(pg, entry); - snapshot_texture_indices(pg, entry); - - if (prim_rw.num_indices > 0) { - entry->draw_mode = RW_DRAW_INDEXED; - entry->draw_count = prim_rw.num_indices; - entry->index_indirect_offset = pgraph_vk_update_index_buffer( - pg, prim_rw.indices, prim_rw.num_indices * sizeof(uint32_t)); - } else if (pg->draw_arrays_length > 1) { - VkDrawIndirectCommand cmds[pg->draw_arrays_length]; - for (int i = 0; i < pg->draw_arrays_length; i++) { - cmds[i] = (VkDrawIndirectCommand){ - .vertexCount = pg->draw_arrays_count[i], - .instanceCount = 1, - .firstVertex = pg->draw_arrays_start[i], - .firstInstance = 0, - }; - } - entry->draw_mode = RW_DRAW_INDIRECT; - entry->draw_count = pg->draw_arrays_length; - entry->index_indirect_offset = pgraph_vk_update_index_buffer( - pg, cmds, sizeof(cmds)); - } else { - entry->draw_mode = RW_DRAW_DIRECT; - entry->draw_count = 1; - entry->vertex_count = pg->draw_arrays_count[0]; - entry->first_vertex = pg->draw_arrays_start[0]; - } - - uint32_t control_0 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0); - entry->color_write = - (control_0 & NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE) || - (control_0 & NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE) || - (control_0 & NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE) || - (control_0 & NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE); - entry->depth_test = !!(control_0 & NV_PGRAPH_CONTROL_0_ZENABLE); - entry->stencil_test = - !!(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1) & - NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE); - - return true; -} - -static bool try_snapshot_inline_elements(NV2AState *d, - ReorderWindowEntry *entry) -{ - PGRAPHState *pg = &d->pgraph; - PGRAPHVkState *r = pg->vk_renderer_state; - - if (!(r->color_binding || r->zeta_binding)) { - return false; - } - - nv2a_profile_inc_counter(NV2A_PROF_INLINE_ELEMENTS); - - PrimAssemblyState assembly = { - .primitive_mode = pg->primitive_mode, - .polygon_mode = (enum ShaderPolygonMode)GET_MASK( - pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER), - NV_PGRAPH_SETUPRASTER_FRONTFACEMODE), - .last_provoking = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), - NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX) == - NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_LAST, - .flat_shading = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), - NV_PGRAPH_CONTROL_3_SHADEMODE) == - NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT, - }; - - uint32_t *draw_indices = pg->inline_elements; - unsigned int draw_index_count = pg->inline_elements_length; - PrimRewrite prim_rw = pgraph_prim_rewrite_indexed( - &r->prim_rewrite_buf, assembly, pg->inline_elements, - pg->inline_elements_length); - if (prim_rw.num_indices > 0) { - draw_indices = prim_rw.indices; - draw_index_count = prim_rw.num_indices; - } - - size_t index_data_size = draw_index_count * sizeof(uint32_t); - ensure_buffer_space(pg, BUFFER_INDEX_STAGING, index_data_size); - - uint32_t min_element = UINT32_MAX; - uint32_t max_element = 0; - for (unsigned int i = 0; i < draw_index_count; i++) { - max_element = MAX(draw_indices[i], max_element); - min_element = MIN(draw_indices[i], min_element); - } - - pgraph_vk_bind_vertex_attributes( - d, min_element, max_element, false, 0, - draw_indices[draw_index_count - 1]); - sync_vertex_ram_buffer(pg); - VertexBufferRemap remap = remap_unaligned_attributes(pg, max_element + 1); - - begin_pre_draw(pg); - if (r->async_draw_skip || - reorder_uses_bindless_direct_surface_textures(r) || - !r->pipeline_binding || r->descriptor_set_index == 0) { - return false; - } - - copy_remapped_attributes_to_inline_buffer(pg, remap, 0, max_element + 1); - - entry->pipeline_binding = r->pipeline_binding; - entry->layout = r->pipeline_binding->layout; - entry->descriptor_set = r->descriptor_sets[r->descriptor_set_index - 1]; - entry->has_dynamic_line_width = r->pipeline_binding->has_dynamic_line_width; - if (entry->has_dynamic_line_width) { - entry->line_width = - clamp_line_width_to_device_limits(pg, pg->surface_scale_factor); - } - - snapshot_vertex_buffers(pg, entry, remap.attributes, 0); - snapshot_draw_state(pg, entry); - snapshot_push_constants(pg, entry); - snapshot_texture_indices(pg, entry); - - entry->draw_mode = RW_DRAW_INDEXED; - entry->draw_count = draw_index_count; - entry->index_indirect_offset = pgraph_vk_update_index_buffer( - pg, draw_indices, index_data_size); - - uint32_t control_0 = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0); - entry->color_write = - (control_0 & NV_PGRAPH_CONTROL_0_ALPHA_WRITE_ENABLE) || - (control_0 & NV_PGRAPH_CONTROL_0_RED_WRITE_ENABLE) || - (control_0 & NV_PGRAPH_CONTROL_0_GREEN_WRITE_ENABLE) || - (control_0 & NV_PGRAPH_CONTROL_0_BLUE_WRITE_ENABLE); - entry->depth_test = !!(control_0 & NV_PGRAPH_CONTROL_0_ZENABLE); - entry->stencil_test = - !!(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_1) & - NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE); - - return true; -} - -static int compare_reorder_entries(const void *a, const void *b) -{ - const ReorderWindowEntry *left = a; - const ReorderWindowEntry *right = b; - - if (left->group_order < right->group_order) { - return -1; - } - if (left->group_order > right->group_order) { - return 1; - } - return left->sequence_number - right->sequence_number; -} - -static void emit_reorder_entry(PGRAPHState *pg, ReorderWindowEntry *entry, - PipelineBinding *prev_pipeline) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - bool pipeline_changed = (entry->pipeline_binding != prev_pipeline); - - if (!r->in_render_pass) { - begin_render_pass(pg); - pipeline_changed = true; - } - - if (pipeline_changed) { - nv2a_profile_inc_counter(NV2A_PROF_PIPELINE_BIND); - vkCmdBindPipeline(r->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - entry->pipeline_binding->pipeline); - entry->pipeline_binding->draw_time = pg->draw_time; - vkCmdSetViewport(r->command_buffer, 0, 1, &entry->viewport); - vkCmdSetScissor(r->command_buffer, 0, 1, &entry->scissor); - if (entry->has_dynamic_line_width) { - vkCmdSetLineWidth(r->command_buffer, entry->line_width); - } - } - -#if OPT_BINDLESS_TEXTURES - if (entry->use_bindless_textures) { - vkCmdBindDescriptorSets(r->command_buffer, - VK_PIPELINE_BIND_POINT_GRAPHICS, - entry->layout, 0, 1, - &r->bindless_descriptor_set, 0, NULL); - r->bindless_set_bound = true; - vkCmdPushConstants(r->command_buffer, entry->layout, - VK_SHADER_STAGE_FRAGMENT_BIT, r->tex_push_offset, - sizeof(entry->tex_bindless_indices), - entry->tex_bindless_indices); - } -#endif - - vkCmdBindDescriptorSets(r->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - entry->layout, - entry->use_bindless_textures ? 1 : 0, 1, - &entry->descriptor_set, 0, NULL); - - if (entry->use_push_constants && entry->num_push_values > 0) { -#if OPT_BINDLESS_TEXTURES - uint32_t vertex_push_offset = - entry->use_bindless_textures && r->tex_push_offset == 0 - ? NV2A_MAX_TEXTURES * sizeof(uint32_t) - : 0; -#else - uint32_t vertex_push_offset = 0; -#endif - vkCmdPushConstants(r->command_buffer, entry->layout, - VK_SHADER_STAGE_VERTEX_BIT, vertex_push_offset, - entry->num_push_values * 4 * sizeof(float), - entry->push_values); - } - - if (entry->num_vertex_bindings > 0) { - vkCmdBindVertexBuffers(r->command_buffer, 0, - entry->num_vertex_bindings, - entry->vertex_buffers, entry->vertex_offsets); - } - - switch (entry->draw_mode) { - case RW_DRAW_INDEXED: - vkCmdBindIndexBuffer(r->command_buffer, - r->storage_buffers[BUFFER_INDEX].buffer, - entry->index_indirect_offset, - VK_INDEX_TYPE_UINT32); - vkCmdDrawIndexed(r->command_buffer, entry->draw_count, 1, 0, 0, 0); - break; - case RW_DRAW_INDIRECT: - vkCmdDrawIndirect(r->command_buffer, - r->storage_buffers[BUFFER_INDEX].buffer, - entry->index_indirect_offset, entry->draw_count, - sizeof(VkDrawIndirectCommand)); - break; - case RW_DRAW_DIRECT: - vkCmdDraw(r->command_buffer, entry->vertex_count, 1, - entry->first_vertex, 0); - break; - } -} - -static void flush_reorder_window_internal(NV2AState *d) -{ - PGRAPHState *pg = &d->pgraph; - PGRAPHVkState *r = pg->vk_renderer_state; - ReorderWindow *window = &r->reorder_window; - - if (window->count == 0) { - return; - } - - qsort(window->entries, window->count, sizeof(window->entries[0]), - compare_reorder_entries); - - pgraph_vk_ensure_command_buffer(pg); - - PipelineBinding *prev_pipeline = NULL; - for (int i = 0; i < window->count; i++) { - ReorderWindowEntry *entry = &window->entries[i]; - emit_reorder_entry(pg, entry, prev_pipeline); - prev_pipeline = entry->pipeline_binding; - - pg->draw_time++; - if (r->color_binding && entry->color_write) { - r->color_binding->draw_time = pg->draw_time; - } - if (r->zeta_binding && (entry->depth_test || entry->stencil_test)) { - r->zeta_binding->draw_time = pg->draw_time; - } - - pgraph_vk_set_surface_dirty(pg, entry->color_write, - entry->depth_test || entry->stencil_test); - } - - r->pipeline_binding = prev_pipeline; - r->pipeline_binding_changed = false; - window->count = 0; - window->active = false; - window->num_seen_pipelines = 0; - window->next_group = 0; -} -#endif - -void pgraph_vk_flush_reorder_window(NV2AState *d) -{ -#if OPT_REORDER_SAFE_WINDOWS - PGRAPHVkState *r = d->pgraph.vk_renderer_state; - if (r->reorder_window.count > 0) { - flush_reorder_window_internal(d); - } - r->reorder_window.active = false; -#endif -} - void pgraph_vk_set_surface_dirty(PGRAPHState *pg, bool color, bool zeta) { NV2A_DPRINTF("pgraph_set_surface_dirty(%d, %d) -- %d %d\n", color, zeta, @@ -2968,18 +1911,12 @@ void pgraph_vk_set_surface_dirty(PGRAPHState *pg, bool color, bool zeta) if (r->color_binding) { r->color_binding->draw_dirty |= color; - if (color) { - r->color_binding->draw_generation++; - } r->color_binding->frame_time = pg->frame_time; r->color_binding->cleared = false; } if (r->zeta_binding) { r->zeta_binding->draw_dirty |= zeta; - if (zeta) { - r->zeta_binding->draw_generation++; - } r->zeta_binding->frame_time = pg->frame_time; r->zeta_binding->cleared = false; } @@ -3027,6 +1964,16 @@ static void get_size_and_count_for_format(VkFormat fmt, size_t *size, size_t *co *count = table[fmt].count; } +typedef struct VertexBufferRemap { + uint16_t attributes; + size_t buffer_space_required; + struct { + VkDeviceAddress offset; + VkDeviceSize old_stride; + VkDeviceSize new_stride; + } map[NV2A_VERTEXSHADER_ATTRIBUTES]; +} VertexBufferRemap; + static VertexBufferRemap remap_unaligned_attributes(PGRAPHState *pg, uint32_t num_vertices) { @@ -3092,63 +2039,6 @@ static VertexBufferRemap remap_unaligned_attributes(PGRAPHState *pg, return remap; } -#ifdef __aarch64__ -static inline bool copy_remapped_attribute_arm64(uint8_t *dst, - const uint8_t *src, - VkDeviceSize old_stride, - VkDeviceSize new_stride, - uint32_t num_vertices) -{ - switch (new_stride) { - case 4: - for (uint32_t vertex_id = 0; vertex_id < num_vertices; vertex_id++) { - uint32_t word; - - memcpy(&word, src, sizeof(word)); - memcpy(dst, &word, sizeof(word)); - - dst += new_stride; - src += old_stride; - } - return true; - - case 8: - for (uint32_t vertex_id = 0; vertex_id < num_vertices; vertex_id++) { - vst1_u8(dst, vld1_u8(src)); - - dst += new_stride; - src += old_stride; - } - return true; - - case 12: - for (uint32_t vertex_id = 0; vertex_id < num_vertices; vertex_id++) { - uint32_t tail_word; - - vst1_u8(dst, vld1_u8(src)); - memcpy(&tail_word, src + 8, sizeof(tail_word)); - memcpy(dst + 8, &tail_word, sizeof(tail_word)); - - dst += new_stride; - src += old_stride; - } - return true; - - case 16: - for (uint32_t vertex_id = 0; vertex_id < num_vertices; vertex_id++) { - vst1q_u8(dst, vld1q_u8(src)); - - dst += new_stride; - src += old_stride; - } - return true; - - default: - return false; - } -} -#endif - static void copy_remapped_attributes_to_inline_buffer(PGRAPHState *pg, VertexBufferRemap remap, uint32_t start_vertex, @@ -3183,16 +2073,6 @@ static void copy_remapped_attributes_to_inline_buffer(PGRAPHState *pg, uint8_t *out_ptr = buffer->mapped + attr_buffer_offset; uint8_t *in_ptr = d->vram_ptr + r->vertex_attribute_offsets[attr_id]; -#ifdef __aarch64__ - if (copy_remapped_attribute_arm64(out_ptr, in_ptr, - remap.map[attr_id].old_stride, - remap.map[attr_id].new_stride, - num_vertices)) { - r->vertex_attribute_offsets[attr_id] = attr_buffer_offset; - continue; - } -#endif - for (int vertex_id = 0; vertex_id < num_vertices; vertex_id++) { memcpy(out_ptr, in_ptr, remap.map[attr_id].new_stride); out_ptr += remap.map[attr_id].new_stride; @@ -3263,10 +2143,6 @@ void pgraph_vk_flush_draw(NV2AState *d) } begin_pre_draw(pg); - if (r->async_draw_skip) { - NV2A_VK_DGROUP_END(); - return; - } copy_remapped_attributes_to_inline_buffer(pg, remap, 0, max_element); pgraph_vk_begin_debug_marker(r, r->command_buffer, RGBA_BLUE, "Draw Arrays"); @@ -3328,10 +2204,6 @@ void pgraph_vk_flush_draw(NV2AState *d) VertexBufferRemap remap = remap_unaligned_attributes(pg, max_element + 1); begin_pre_draw(pg); - if (r->async_draw_skip) { - NV2A_VK_DGROUP_END(); - return; - } copy_remapped_attributes_to_inline_buffer(pg, remap, 0, max_element + 1); VkDeviceSize buffer_offset = pgraph_vk_update_index_buffer( pg, draw_indices, index_data_size); @@ -3380,10 +2252,6 @@ void pgraph_vk_flush_draw(NV2AState *d) } begin_pre_draw(pg); - if (r->async_draw_skip) { - NV2A_VK_DGROUP_END(); - return; - } VkDeviceSize buffer_offset = pgraph_vk_update_vertex_inline_buffer( pg, data, sizes, r->num_active_vertex_attribute_descriptions); pgraph_vk_begin_debug_marker(r, r->command_buffer, RGBA_BLUE, @@ -3448,10 +2316,6 @@ void pgraph_vk_flush_draw(NV2AState *d) } begin_pre_draw(pg); - if (r->async_draw_skip) { - NV2A_VK_DGROUP_END(); - return; - } void *inline_array_data = pg->inline_array; VkDeviceSize buffer_offset = pgraph_vk_update_vertex_inline_buffer( pg, &inline_array_data, &inline_array_data_size, 1); diff --git a/hw/xbox/nv2a/pgraph/vk/glsl.c b/hw/xbox/nv2a/pgraph/vk/glsl.c index 2616fc75a2..d7aef1b590 100644 --- a/hw/xbox/nv2a/pgraph/vk/glsl.c +++ b/hw/xbox/nv2a/pgraph/vk/glsl.c @@ -17,8 +17,6 @@ * License along with this library; if not, see . */ -#include "qemu/osdep.h" -#include "qemu/fast-hash.h" #include "ui/xemu-settings.h" #include "renderer.h" @@ -26,168 +24,6 @@ #include #include -#define SPIRV_CACHE_VERSION 1 -#define SPIRV_CACHE_MAGIC 0x53505643 /* 'SPVC' */ - -typedef struct SpirvCacheEntry { - uint64_t hash; - uint32_t spirv_len; - uint8_t *spirv_data; -} SpirvCacheEntry; - -static GHashTable *spirv_cache; -static bool spirv_cache_dirty; -static bool spirv_atexit_registered; - -static char *get_spirv_cache_path(void) -{ - const char *base = xemu_settings_get_base_path(); - if (!base) { - return NULL; - } - return g_build_filename(base, "spirv_cache.bin", NULL); -} - -static void spirv_cache_entry_free(gpointer data) -{ - SpirvCacheEntry *entry = data; - - if (!entry) { - return; - } - - g_free(entry->spirv_data); - g_free(entry); -} - -static void load_spirv_cache(void) -{ - if (spirv_cache) { - return; - } - - spirv_cache = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, - spirv_cache_entry_free); - - g_autofree char *path = get_spirv_cache_path(); - if (!path) { - return; - } - - FILE *f = fopen(path, "rb"); - if (!f) { - return; - } - - uint32_t magic, version, count; - if (fread(&magic, 4, 1, f) != 1 || magic != SPIRV_CACHE_MAGIC || - fread(&version, 4, 1, f) != 1 || version != SPIRV_CACHE_VERSION || - fread(&count, 4, 1, f) != 1) { - fclose(f); - return; - } - - for (uint32_t i = 0; i < count; i++) { - uint64_t hash; - uint32_t spirv_len; - if (fread(&hash, 8, 1, f) != 1 || - fread(&spirv_len, 4, 1, f) != 1 || spirv_len == 0 || - spirv_len > 1024 * 1024) { - break; - } - - SpirvCacheEntry *entry = g_malloc(sizeof(*entry)); - entry->hash = hash; - entry->spirv_len = spirv_len; - entry->spirv_data = g_malloc(spirv_len); - - if (fread(entry->spirv_data, 1, spirv_len, f) != spirv_len) { - spirv_cache_entry_free(entry); - break; - } - - g_hash_table_insert(spirv_cache, &entry->hash, entry); - } - - fclose(f); -} - -static void save_spirv_cache(void) -{ - if (!spirv_cache || !spirv_cache_dirty) { - return; - } - - g_autofree char *path = get_spirv_cache_path(); - if (!path) { - return; - } - - FILE *f = fopen(path, "wb"); - if (!f) { - return; - } - - bool ok = true; - uint32_t magic = SPIRV_CACHE_MAGIC; - uint32_t version = SPIRV_CACHE_VERSION; - uint32_t count = g_hash_table_size(spirv_cache); - - ok = ok && fwrite(&magic, 4, 1, f) == 1; - ok = ok && fwrite(&version, 4, 1, f) == 1; - ok = ok && fwrite(&count, 4, 1, f) == 1; - - GHashTableIter iter; - gpointer key, value; - g_hash_table_iter_init(&iter, spirv_cache); - while (ok && g_hash_table_iter_next(&iter, &key, &value)) { - SpirvCacheEntry *entry = value; - ok = ok && fwrite(&entry->hash, 8, 1, f) == 1; - ok = ok && fwrite(&entry->spirv_len, 4, 1, f) == 1; - ok = ok && fwrite(entry->spirv_data, 1, entry->spirv_len, f) == - entry->spirv_len; - } - - if (fclose(f) == 0 && ok) { - spirv_cache_dirty = false; - } else { - remove(path); - } -} - -static GByteArray *spirv_cache_lookup(const char *glsl) -{ - if (!spirv_cache) { - return NULL; - } - - uint64_t hash = fast_hash((const uint8_t *)glsl, strlen(glsl)); - SpirvCacheEntry *entry = g_hash_table_lookup(spirv_cache, &hash); - if (!entry) { - return NULL; - } - - GByteArray *spirv = g_byte_array_sized_new(entry->spirv_len); - g_byte_array_append(spirv, entry->spirv_data, entry->spirv_len); - return spirv; -} - -static void spirv_cache_insert(const char *glsl, GByteArray *spirv) -{ - if (!spirv_cache || !spirv) { - return; - } - - uint64_t hash = fast_hash((const uint8_t *)glsl, strlen(glsl)); - SpirvCacheEntry *entry = g_malloc(sizeof(*entry)); - entry->hash = hash; - entry->spirv_len = spirv->len; - entry->spirv_data = g_malloc(spirv->len); - memcpy(entry->spirv_data, spirv->data, spirv->len); - g_hash_table_insert(spirv_cache, &entry->hash, entry); - spirv_cache_dirty = true; -} - static const glslang_resource_t resource_limits = { .max_lights = 32, .max_clip_planes = 6, @@ -297,21 +133,10 @@ static const glslang_resource_t void pgraph_vk_init_glsl_compiler(void) { glslang_initialize_process(); - load_spirv_cache(); - - if (!spirv_atexit_registered) { - atexit(save_spirv_cache); - spirv_atexit_registered = true; - } } void pgraph_vk_finalize_glsl_compiler(void) { - save_spirv_cache(); - if (spirv_cache) { - g_hash_table_destroy(spirv_cache); - spirv_cache = NULL; - } glslang_finalize_process(); } @@ -545,12 +370,8 @@ ShaderModuleInfo *pgraph_vk_create_shader_module_from_glsl( ShaderModuleInfo *info = g_malloc0(sizeof(*info)); info->refcnt = 0; info->glsl = strdup(glsl); - info->spirv = spirv_cache_lookup(glsl); - if (!info->spirv) { - info->spirv = pgraph_vk_compile_glsl_to_spv( - vk_shader_stage_to_glslang_stage(stage), glsl); - spirv_cache_insert(glsl, info->spirv); - } + info->spirv = pgraph_vk_compile_glsl_to_spv( + vk_shader_stage_to_glslang_stage(stage), glsl); info->module = pgraph_vk_create_shader_module_from_spv(r, info->spirv); init_layout_from_spv(info); return info; diff --git a/hw/xbox/nv2a/pgraph/vk/image.c b/hw/xbox/nv2a/pgraph/vk/image.c index 1e2211a88e..e2dec87f59 100644 --- a/hw/xbox/nv2a/pgraph/vk/image.c +++ b/hw/xbox/nv2a/pgraph/vk/image.c @@ -202,15 +202,6 @@ void pgraph_vk_transition_image_layout(PGRAPHState *pg, VkCommandBuffer cmd, sourceStage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // Depth Read-Only -> Src - } else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL && - newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - - sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // Depth -> Dst } else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { @@ -219,14 +210,6 @@ void pgraph_vk_transition_image_layout(PGRAPHState *pg, VkCommandBuffer cmd, sourceStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // Depth Read-Only -> Dst - } else if (oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL && - newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - // Src -> Color } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { @@ -243,22 +226,6 @@ void pgraph_vk_transition_image_layout(PGRAPHState *pg, VkCommandBuffer cmd, sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - // Src -> Depth Read-Only - } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && - newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - - // Dst -> Depth Read-Only - } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && - newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - // Src -> Dst } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { diff --git a/hw/xbox/nv2a/pgraph/vk/instance.c b/hw/xbox/nv2a/pgraph/vk/instance.c index 400f443c26..5d59344520 100644 --- a/hw/xbox/nv2a/pgraph/vk/instance.c +++ b/hw/xbox/nv2a/pgraph/vk/instance.c @@ -65,6 +65,16 @@ static char const *const validation_layers[] = { "VK_LAYER_KHRONOS_validation", }; +static char const *const required_device_extensions[] = { +#ifdef WIN32 + VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME, +#else + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, +#endif +}; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, @@ -332,23 +342,12 @@ get_available_device_extensions(VkPhysicalDevice device) static StringArray *get_required_device_extension_names(void) { - StringArray *extensions = g_array_sized_new(FALSE, FALSE, sizeof(char *), 2); + StringArray *extensions = + g_array_sized_new(FALSE, FALSE, sizeof(char *), + ARRAY_SIZE(required_device_extensions)); -#ifdef WIN32 - static char const *const required_device_extensions[] = { - VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME, - }; g_array_append_vals(extensions, required_device_extensions, ARRAY_SIZE(required_device_extensions)); -#elif HAVE_EXTERNAL_MEMORY - static char const *const required_device_extensions[] = { - VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, - }; - g_array_append_vals(extensions, required_device_extensions, - ARRAY_SIZE(required_device_extensions)); -#endif return extensions; } @@ -366,16 +365,6 @@ static void add_optional_device_extension_names( r->memory_budget_extension_enabled = add_extension_if_available( available_extensions, enabled_extension_names, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); - -#if OPT_BINDLESS_TEXTURES - if (r->device_props.apiVersion >= VK_API_VERSION_1_2) { - r->bindless_textures_supported = true; - } else { - r->bindless_textures_supported = add_extension_if_available( - available_extensions, enabled_extension_names, - VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); - } -#endif } static bool check_device_support_required_extensions(VkPhysicalDevice device) @@ -383,24 +372,7 @@ static bool check_device_support_required_extensions(VkPhysicalDevice device) g_autoptr(VkExtensionPropertiesArray) available_extensions = get_available_device_extensions(device); -#if !(defined(WIN32) || HAVE_EXTERNAL_MEMORY) - return true; -#else -#ifdef WIN32 - static char const *const required_device_extensions[] = { - VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME, - }; -#else - static char const *const required_device_extensions[] = { - VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, - }; -#endif - const int required_device_extensions_len = - ARRAY_SIZE(required_device_extensions); - - for (int i = 0; i < required_device_extensions_len; i++) { + for (int i = 0; i < ARRAY_SIZE(required_device_extensions); i++) { if (!is_extension_available(available_extensions, required_device_extensions[i])) { fprintf(stderr, "required device extension not found: %s\n", @@ -408,403 +380,11 @@ static bool check_device_support_required_extensions(VkPhysicalDevice device) return false; } } -#endif return true; } -static void report_device_incompatibility(VkPhysicalDevice device, - const char *reason) -{ - VkPhysicalDeviceProperties props; - vkGetPhysicalDeviceProperties(device, &props); - - fprintf(stderr, "Vulkan device rejected (%s): %s\n", props.deviceName, - reason); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "Vulkan device rejected (%s): %s", props.deviceName, - reason); -#endif -} - -#ifdef __ANDROID__ -static void log_android_format_support_failure(const char *label, - VkFormat format, - VkImageUsageFlags usage, - VkFormatFeatureFlags required, - VkFormatProperties props, - VkResult image_result) -{ - fprintf(stderr, - "Android Vulkan format issue [%s]: format=%d usage=0x%x " - "required_optimal=0x%x optimal=0x%x linear=0x%x buffer=0x%x " - "imageFormatResult=%d\n", - label, (int)format, (unsigned int)usage, (unsigned int)required, - (unsigned int)props.optimalTilingFeatures, - (unsigned int)props.linearTilingFeatures, - (unsigned int)props.bufferFeatures, (int)image_result); - __android_log_print( - ANDROID_LOG_WARN, "xemu-android", - "vk format issue [%s]: format=%d usage=0x%x required_optimal=0x%x " - "optimal=0x%x linear=0x%x buffer=0x%x imageFormatResult=%d", - label, (int)format, (unsigned int)usage, (unsigned int)required, - (unsigned int)props.optimalTilingFeatures, - (unsigned int)props.linearTilingFeatures, - (unsigned int)props.bufferFeatures, (int)image_result); -} -#endif - -static bool format_already_checked(const VkFormat *formats, size_t count, - VkFormat format) -{ - for (size_t i = 0; i < count; i++) { - if (formats[i] == format) { - return true; - } - } - - return false; -} - -static bool check_format_supports_features(VkPhysicalDevice device, - VkFormat format, - VkFormatFeatureFlags features) -{ - VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(device, format, &props); - - return (props.optimalTilingFeatures & features) == features; -} - -static bool check_image_format_usage_supported(VkPhysicalDevice device, - VkFormat format, - VkImageUsageFlags usage) -{ - VkPhysicalDeviceImageFormatInfo2 info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = usage, - }; - VkImageFormatProperties2 props = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; - - return vkGetPhysicalDeviceImageFormatProperties2(device, &info, &props) == - VK_SUCCESS; -} - -static bool check_texture_formats_supported_internal(VkPhysicalDevice device, - bool report_failures) -{ - VkFormat checked_formats[ARRAY_SIZE(kelvin_color_format_vk_map)]; - size_t num_checked_formats = 0; - const VkImageUsageFlags usage = - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - - memset(checked_formats, 0, sizeof(checked_formats)); - - for (int i = 0; i < ARRAY_SIZE(kelvin_color_format_vk_map); i++) { - VkFormat format = kelvin_color_format_vk_map[i].vk_format; - - if (format == VK_FORMAT_UNDEFINED || - format_already_checked(checked_formats, num_checked_formats, - format)) { - continue; - } - - checked_formats[num_checked_formats++] = format; - - if (!check_format_supports_features(device, format, - VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) || - !check_image_format_usage_supported(device, format, usage)) { - if (report_failures) { - char reason[160]; - - snprintf(reason, sizeof(reason), - "sampled texture uploads need VkFormat %d to support " - "optimal sampled images and transfer-dst usage", - format); - report_device_incompatibility(device, reason); - } - return false; - } - } - - return true; -} - -static bool check_texture_formats_supported(VkPhysicalDevice device) -{ - return check_texture_formats_supported_internal(device, true); -} - -static bool check_surface_format_supported_internal(VkPhysicalDevice device, - const SurfaceFormatInfo *format, - bool report_failures, - const char *reason) -{ - VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - format->usage; - - bool supported = - check_image_format_usage_supported(device, format->vk_format, usage) && - check_format_supports_features(device, format->vk_format, - VK_FORMAT_FEATURE_BLIT_SRC_BIT | - VK_FORMAT_FEATURE_BLIT_DST_BIT); - if (!supported && report_failures) { - report_device_incompatibility(device, reason); - } - return supported; -} - -static bool check_surface_formats_supported_internal(VkPhysicalDevice device, - bool report_failures) -{ - VkFormat checked_formats[ARRAY_SIZE(kelvin_surface_color_format_vk_map)]; - size_t num_checked_formats = 0; - - memset(checked_formats, 0, sizeof(checked_formats)); - - for (int i = 0; i < ARRAY_SIZE(kelvin_surface_color_format_vk_map); i++) { - const SurfaceFormatInfo *format = &kelvin_surface_color_format_vk_map[i]; - - if (!format->host_bytes_per_pixel || - format_already_checked(checked_formats, num_checked_formats, - format->vk_format)) { - continue; - } - - checked_formats[num_checked_formats++] = format->vk_format; - - if (!check_surface_format_supported_internal( - device, format, report_failures, - "surface format needs sampled, transfer, attachment, and " - "blit support")) { - return false; - } - } - - if (!check_surface_format_supported_internal( - device, &zeta_d16, report_failures, - "Z16 surfaces need sampled, transfer, attachment, and blit support")) { - return false; - } - - if (!check_surface_format_supported_internal( - device, &zeta_d24_unorm_s8_uint, false, - "Z24S8 surfaces need either D24_UNORM_S8_UINT or " - "D32_SFLOAT_S8_UINT with sampled, transfer, attachment, and blit " - "support") && - !check_surface_format_supported_internal( - device, &zeta_d32_sfloat_s8_uint, false, - "Z24S8 surfaces need either D24_UNORM_S8_UINT or " - "D32_SFLOAT_S8_UINT with sampled, transfer, attachment, and blit " - "support")) { - if (report_failures) { - report_device_incompatibility( - device, - "Z24S8 surfaces need either D24_UNORM_S8_UINT or " - "D32_SFLOAT_S8_UINT with sampled, transfer, attachment, and " - "blit support"); - } - return false; - } - - return true; -} - -static bool check_surface_formats_supported(VkPhysicalDevice device) -{ - return check_surface_formats_supported_internal(device, true); -} - -#ifdef __ANDROID__ -static void log_android_texture_format_diagnostics(VkPhysicalDevice device) -{ - VkFormat checked_formats[ARRAY_SIZE(kelvin_color_format_vk_map)]; - size_t num_checked_formats = 0; - const VkImageUsageFlags usage = - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - - memset(checked_formats, 0, sizeof(checked_formats)); - - for (int i = 0; i < ARRAY_SIZE(kelvin_color_format_vk_map); i++) { - VkFormat format = kelvin_color_format_vk_map[i].vk_format; - VkFormatProperties props; - VkResult image_result; - bool features_ok; - bool usage_ok; - - if (format == VK_FORMAT_UNDEFINED || - format_already_checked(checked_formats, num_checked_formats, - format)) { - continue; - } - - checked_formats[num_checked_formats++] = format; - vkGetPhysicalDeviceFormatProperties(device, format, &props); - features_ok = (props.optimalTilingFeatures & - VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == - VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; - - VkPhysicalDeviceImageFormatInfo2 info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = usage, - }; - VkImageFormatProperties2 image_props = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; - image_result = vkGetPhysicalDeviceImageFormatProperties2( - device, &info, &image_props); - usage_ok = (image_result == VK_SUCCESS); - - if (!features_ok || !usage_ok) { - log_android_format_support_failure( - "texture", format, usage, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT, - props, image_result); - } - } -} - -static void log_android_surface_format_diagnostics(VkPhysicalDevice device) -{ - VkFormat checked_formats[ARRAY_SIZE(kelvin_surface_color_format_vk_map)]; - size_t num_checked_formats = 0; - const VkFormatFeatureFlags required_features = - VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; - - memset(checked_formats, 0, sizeof(checked_formats)); - - for (int i = 0; i < ARRAY_SIZE(kelvin_surface_color_format_vk_map); i++) { - const SurfaceFormatInfo *format = &kelvin_surface_color_format_vk_map[i]; - VkImageUsageFlags usage; - VkFormatProperties props; - VkResult image_result; - bool features_ok; - bool usage_ok; - - if (!format->host_bytes_per_pixel || - format_already_checked(checked_formats, num_checked_formats, - format->vk_format)) { - continue; - } - - checked_formats[num_checked_formats++] = format->vk_format; - usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | format->usage; - vkGetPhysicalDeviceFormatProperties(device, format->vk_format, &props); - features_ok = - (props.optimalTilingFeatures & required_features) == - required_features; - - VkPhysicalDeviceImageFormatInfo2 info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = format->vk_format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = usage, - }; - VkImageFormatProperties2 image_props = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; - image_result = vkGetPhysicalDeviceImageFormatProperties2( - device, &info, &image_props); - usage_ok = (image_result == VK_SUCCESS); - - if (!features_ok || !usage_ok) { - log_android_format_support_failure("surface-color", - format->vk_format, usage, - required_features, props, - image_result); - } - } - - const struct { - const char *label; - const SurfaceFormatInfo *format; - } zeta_formats[] = { - { "surface-zeta-d16", &zeta_d16 }, - { "surface-zeta-d24s8", &zeta_d24_unorm_s8_uint }, - { "surface-zeta-d32s8", &zeta_d32_sfloat_s8_uint }, - }; - - for (int i = 0; i < ARRAY_SIZE(zeta_formats); i++) { - const SurfaceFormatInfo *format = zeta_formats[i].format; - VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - format->usage; - VkFormatProperties props; - VkResult image_result; - bool features_ok; - bool usage_ok; - - vkGetPhysicalDeviceFormatProperties(device, format->vk_format, &props); - features_ok = - (props.optimalTilingFeatures & required_features) == - required_features; - - VkPhysicalDeviceImageFormatInfo2 info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = format->vk_format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = usage, - }; - VkImageFormatProperties2 image_props = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; - image_result = vkGetPhysicalDeviceImageFormatProperties2( - device, &info, &image_props); - usage_ok = (image_result == VK_SUCCESS); - - if (!features_ok || !usage_ok) { - log_android_format_support_failure(zeta_formats[i].label, - format->vk_format, usage, - required_features, props, - image_result); - } - } -} - -static void log_android_device_format_diagnostics(VkPhysicalDevice device) -{ - bool textures_ok = check_texture_formats_supported_internal(device, false); - bool surfaces_ok = check_surface_formats_supported_internal(device, false); - - if (textures_ok && surfaces_ok) { - fprintf(stderr, - "Android Vulkan device passed full xemu texture/surface " - "format compatibility checks\n"); - __android_log_print( - ANDROID_LOG_INFO, "xemu-android", - "Selected Vulkan device passed full xemu texture/surface format " - "compatibility checks"); - return; - } - - fprintf(stderr, - "Warning: Android Vulkan device is running in best-effort mode; " - "full xemu format compatibility checks failed\n"); - __android_log_print( - ANDROID_LOG_WARN, "xemu-android", - "Selected Vulkan device is running in best-effort mode; full xemu " - "format compatibility checks failed"); - - log_android_texture_format_diagnostics(device); - log_android_surface_format_diagnostics(device); -} -#endif - -static bool is_device_minimally_compatible(VkPhysicalDevice device) +static bool is_device_compatible(VkPhysicalDevice device) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(device, &props); @@ -816,19 +396,7 @@ static bool is_device_minimally_compatible(VkPhysicalDevice device) return is_queue_family_indicies_complete(indices) && check_device_support_required_extensions(device); -} - -static bool is_device_fully_compatible(VkPhysicalDevice device) -{ -#ifdef __ANDROID__ - return is_device_minimally_compatible(device) && - check_texture_formats_supported_internal(device, false) && - check_surface_formats_supported_internal(device, false); -#else - return is_device_minimally_compatible(device) && - check_texture_formats_supported(device) && - check_surface_formats_supported(device); -#endif + // FIXME: Check formats // FIXME: Check vram } @@ -868,55 +436,20 @@ static bool select_physical_device(PGRAPHState *pg, Error **errp) r->physical_device = VK_NULL_HANDLE; - bool selected_device_fully_compatible = false; - if (preferred_device_index >= 0 && - is_device_fully_compatible(devices[preferred_device_index])) { + is_device_compatible(devices[preferred_device_index])) { r->physical_device = devices[preferred_device_index]; - selected_device_fully_compatible = true; } else { for (int i = 0; i < num_physical_devices; i++) { - if (is_device_fully_compatible(devices[i])) { + if (is_device_compatible(devices[i])) { r->physical_device = devices[i]; - selected_device_fully_compatible = true; break; } } } if (r->physical_device == VK_NULL_HANDLE) { -#ifdef __ANDROID__ - if (preferred_device_index >= 0 && - is_device_minimally_compatible(devices[preferred_device_index])) { - r->physical_device = devices[preferred_device_index]; - } else { - for (int i = 0; i < num_physical_devices; i++) { - if (is_device_minimally_compatible(devices[i])) { - r->physical_device = devices[i]; - break; - } - } - } - if (r->physical_device == VK_NULL_HANDLE) { - int fallback_index = - preferred_device_index >= 0 ? preferred_device_index : 0; - r->physical_device = devices[fallback_index]; - vkGetPhysicalDeviceProperties(r->physical_device, &r->device_props); - fprintf(stderr, - "Warning: No minimally compatible Vulkan GPU found; " - "trying %s anyway\n", - r->device_props.deviceName); - } - vkGetPhysicalDeviceProperties(r->physical_device, &r->device_props); - if (!selected_device_fully_compatible) { - fprintf(stderr, - "Warning: No fully compatible Vulkan GPU found; trying %s " - "in best-effort mode\n", - r->device_props.deviceName); - } -#else error_setg(errp, "Failed to find a suitable GPU"); return false; -#endif } vkGetPhysicalDeviceProperties(r->physical_device, &r->device_props); @@ -934,12 +467,6 @@ static bool select_physical_device(PGRAPHState *pg, Error **errp) VK_VERSION_MINOR(r->device_props.driverVersion), VK_VERSION_PATCH(r->device_props.driverVersion)); -#ifdef __ANDROID__ - if (!selected_device_fully_compatible) { - log_android_device_format_diagnostics(r->physical_device); - } -#endif - return true; } @@ -1001,7 +528,7 @@ static bool create_logical_device(PGRAPHState *pg, Error **errp) } F(depthClamp, false), F(fillModeNonSolid, false), - F(geometryShader, false), + F(geometryShader, true), F(occlusionQueryPrecise, false), F(samplerAnisotropy, false), F(shaderClipDistance, false), @@ -1061,82 +588,6 @@ static bool create_logical_device(PGRAPHState *pg, Error **errp) void *next_struct = NULL; -#if OPT_BINDLESS_TEXTURES - VkPhysicalDeviceDescriptorIndexingFeatures descriptor_indexing_features; - if (r->bindless_textures_supported) { - VkPhysicalDeviceDescriptorIndexingFeatures descriptor_indexing_query = { - .sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, - }; - VkPhysicalDeviceDescriptorIndexingProperties descriptor_indexing_props = { - .sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES, - }; - VkPhysicalDeviceFeatures2 features2 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, - .pNext = &descriptor_indexing_query, - }; - VkPhysicalDeviceProperties2 props2 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, - .pNext = &descriptor_indexing_props, - }; - - vkGetPhysicalDeviceFeatures2(r->physical_device, &features2); - vkGetPhysicalDeviceProperties2(r->physical_device, &props2); - - bool have_all = - descriptor_indexing_query.descriptorBindingPartiallyBound && - descriptor_indexing_query - .descriptorBindingSampledImageUpdateAfterBind && - descriptor_indexing_props - .maxPerStageDescriptorUpdateAfterBindSampledImages >= - MAX_BINDLESS_TEXTURES; - - if (have_all) { - memset(&descriptor_indexing_features, 0, - sizeof(descriptor_indexing_features)); - descriptor_indexing_features.sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES; - descriptor_indexing_features.descriptorBindingPartiallyBound = - VK_TRUE; - descriptor_indexing_features - .descriptorBindingSampledImageUpdateAfterBind = VK_TRUE; - descriptor_indexing_features.pNext = next_struct; - next_struct = &descriptor_indexing_features; - - if (r->device_props.limits.maxPushConstantsSize >= - NV2A_VERTEXSHADER_ATTRIBUTES * 4 * sizeof(float) + - NV2A_MAX_TEXTURES * sizeof(uint32_t)) { - r->tex_push_offset = - NV2A_VERTEXSHADER_ATTRIBUTES * 4 * sizeof(float); - r->max_vertex_push_attrs = NV2A_VERTEXSHADER_ATTRIBUTES; - } else { - r->tex_push_offset = 0; - r->max_vertex_push_attrs = - NV2A_VERTEXSHADER_ATTRIBUTES - 1; - } - - fprintf(stderr, - "Bindless textures enabled (push offset=%u, " - "max vertex attrs=%d, max samplers=%u)\n", - r->tex_push_offset, r->max_vertex_push_attrs, - descriptor_indexing_props - .maxPerStageDescriptorUpdateAfterBindSampledImages); - } else { - r->bindless_textures_supported = false; - fprintf(stderr, - "Bindless textures disabled (partiallyBound=%d, " - "updateAfterBind=%d, maxSamplers=%u)\n", - descriptor_indexing_query - .descriptorBindingPartiallyBound, - descriptor_indexing_query - .descriptorBindingSampledImageUpdateAfterBind, - descriptor_indexing_props - .maxPerStageDescriptorUpdateAfterBindSampledImages); - } - } -#endif - VkPhysicalDeviceCustomBorderColorFeaturesEXT custom_border_features; if (r->custom_border_color_extension_enabled) { custom_border_features = (VkPhysicalDeviceCustomBorderColorFeaturesEXT){ diff --git a/hw/xbox/nv2a/pgraph/vk/meson.build b/hw/xbox/nv2a/pgraph/vk/meson.build index 524d46b970..24c2474cb9 100644 --- a/hw/xbox/nv2a/pgraph/vk/meson.build +++ b/hw/xbox/nv2a/pgraph/vk/meson.build @@ -5,7 +5,6 @@ specific_ss.add([sdl, volk, libglslang, vma, vulkan, spirv_reflect, gloffscreen, 'blit.c', 'buffer.c', 'command.c', - 'compile_worker.c', 'debug.c', 'display.c', 'draw.c', diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.c b/hw/xbox/nv2a/pgraph/vk/renderer.c index 7b8484f8d5..ba0d079677 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.c +++ b/hw/xbox/nv2a/pgraph/vk/renderer.c @@ -19,7 +19,6 @@ #include "hw/xbox/nv2a/nv2a_int.h" #include "renderer.h" -#include "qapi/error.h" #include "gloffscreen.h" @@ -50,6 +49,8 @@ static void pgraph_vk_init(NV2AState *d, Error **errp) { PGRAPHState *pg = &d->pgraph; + pg->vk_renderer_state = (PGRAPHVkState *)g_malloc0(sizeof(PGRAPHVkState)); + #if HAVE_EXTERNAL_MEMORY bool use_external_memory = false; #ifdef __ANDROID__ @@ -69,27 +70,17 @@ static void pgraph_vk_init(NV2AState *d, Error **errp) glo_set_current(g_gl_context); use_external_memory = pgraph_vk_gl_external_memory_available(); } -#ifdef __ANDROID__ if (!use_external_memory) { +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "pgraph_vk_init: external memory interop unavailable, using CPU display downloads"); - if (g_gl_context) { - glo_context_destroy(g_gl_context); - g_gl_context = NULL; - } - } else { - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "pgraph_vk_init: external memory interop=enabled"); + "pgraph_vk_init: external memory interop unavailable, using download fallback"); +#endif } -#endif -#elif defined(__ANDROID__) +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "pgraph_vk_init: using CPU display downloads (external memory interop disabled at build time)"); + "pgraph_vk_init: external memory interop=%s", + use_external_memory ? "enabled" : "disabled"); #endif - - pg->vk_renderer_state = (PGRAPHVkState *)g_malloc0(sizeof(PGRAPHVkState)); - -#if HAVE_EXTERNAL_MEMORY pg->vk_renderer_state->display.use_external_memory = use_external_memory; #endif @@ -192,7 +183,6 @@ static void pgraph_vk_flush(NV2AState *d) { PGRAPHState *pg = &d->pgraph; - pgraph_vk_flush_reorder_window(d); pgraph_vk_finish(pg, VK_FINISH_REASON_FLUSH); pgraph_vk_surface_flush(d); pgraph_vk_mark_textures_possibly_dirty(d, 0, memory_region_size(d->vram)); diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index 8bd2c2889d..f0c3634ab5 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -43,15 +43,6 @@ #include "glsl.h" #define HAVE_EXTERNAL_MEMORY 0 -#define NUM_GFX_DESCRIPTOR_SETS 8192 -#define MAX_FRAMEBUFFERS 256 -#define FB_CACHE_MAX 32 -#define OPT_BINDLESS_TEXTURES 1 -#define OPT_REORDER_SAFE_WINDOWS 1 -#define OPT_ASYNC_COMPILE 1 -#define REORDER_WINDOW_MAX 64 -#define MAX_BINDLESS_TEXTURES 1024 -#define BINDLESS_STAGE_SLOT_BASE (MAX_BINDLESS_TEXTURES - NV2A_MAX_TEXTURES) typedef struct QueueFamilyIndices { int queue_family; @@ -88,9 +79,6 @@ typedef struct PipelineBinding { VkRenderPass render_pass; unsigned int draw_time; bool has_dynamic_line_width; -#if OPT_ASYNC_COMPILE - bool pending; -#endif } PipelineBinding; enum Buffer { @@ -142,15 +130,12 @@ typedef struct SurfaceBinding { bool draw_dirty; bool download_pending; bool upload_pending; - uint32_t draw_generation; - uint32_t download_generation; BasicSurfaceFormatInfo fmt; SurfaceFormatInfo host_fmt; VkImage image; VkImageView image_view; - VkImageLayout image_layout; VmaAllocation allocation; // Used for scaling @@ -161,21 +146,6 @@ typedef struct SurfaceBinding { bool initialized; } SurfaceBinding; -typedef struct SurfaceImageConfig { - VkFormat format; - uint32_t width, height; - VkImageUsageFlags usage; -} SurfaceImageConfig; - -typedef struct PooledSurfaceImage { - QTAILQ_ENTRY(PooledSurfaceImage) entry; - SurfaceImageConfig config; - VkImage image; - VmaAllocation allocation; - VkImage image_scratch; - VmaAllocation allocation_scratch; -} PooledSurfaceImage; - typedef struct ShaderModuleInfo { int refcnt; char *glsl; @@ -209,21 +179,11 @@ typedef struct ShaderModuleCacheEntry { LruNode node; ShaderModuleCacheKey key; ShaderModuleInfo *module_info; -#if OPT_ASYNC_COMPILE - bool ready; - unsigned int pending_users; -#endif } ShaderModuleCacheEntry; typedef struct ShaderBinding { LruNode node; ShaderState state; -#if OPT_ASYNC_COMPILE - bool ready; - struct ShaderModuleCacheEntry *pending_vsh_entry; - struct ShaderModuleCacheEntry *pending_geom_entry; - struct ShaderModuleCacheEntry *pending_psh_entry; -#endif struct { ShaderModuleInfo *module_info; VshUniformLocs uniform_locs; @@ -237,64 +197,6 @@ typedef struct ShaderBinding { } psh; } ShaderBinding; -#if OPT_ASYNC_COMPILE -typedef struct PipelineCreateParams { - VkDevice device; - VkPipelineCache vk_pipeline_cache; - - VkPipelineShaderStageCreateInfo shader_stages[3]; - int num_shader_stages; - - ShaderModuleInfo *module_infos[3]; - int num_module_infos; - - VkVertexInputBindingDescription - binding_descs[NV2A_VERTEXSHADER_ATTRIBUTES]; - VkVertexInputAttributeDescription - attr_descs[NV2A_VERTEXSHADER_ATTRIBUTES]; - uint32_t num_binding_descs; - uint32_t num_attr_descs; - - VkPrimitiveTopology topology; - - VkPipelineRasterizationStateCreateInfo rasterizer; - VkPipelineDepthStencilStateCreateInfo depth_stencil; - bool has_zeta; - - VkPipelineColorBlendAttachmentState color_blend_attachment; - bool has_color; - float blend_constants[4]; - - VkDynamicState dynamic_states[8]; - int num_dynamic_states; - - bool has_dynamic_line_width; - - VkPipelineLayout layout; - VkRenderPass render_pass; -} PipelineCreateParams; - -typedef enum { - COMPILE_JOB_SHADER_MODULE, - COMPILE_JOB_PIPELINE, -} CompileJobType; - -typedef struct CompileJob { - CompileJobType type; - QSIMPLEQ_ENTRY(CompileJob) entry; - union { - struct { - ShaderModuleCacheEntry *target; - ShaderModuleCacheKey key; - } shader_module; - struct { - PipelineBinding *target; - PipelineCreateParams params; - } pipeline; - }; -} CompileJob; -#endif - typedef struct TextureKey { TextureShape state; hwaddr texture_vram_offset; @@ -322,72 +224,8 @@ typedef struct TextureBinding { uint64_t hash; unsigned int draw_time; uint32_t submit_time; - unsigned int dirty_check_frame; - bool dirty_check_result; -#if OPT_BINDLESS_TEXTURES - uint32_t bindless_slot; - uint32_t bindless_binding; -#endif } TextureBinding; -#if OPT_REORDER_SAFE_WINDOWS -typedef enum ReorderDrawMode { - RW_DRAW_INDEXED, - RW_DRAW_INDIRECT, - RW_DRAW_DIRECT, -} ReorderDrawMode; - -typedef struct ReorderWindowEntry { - PipelineBinding *pipeline_binding; - VkDescriptorSet descriptor_set; - - VkBuffer vertex_buffers[NV2A_VERTEXSHADER_ATTRIBUTES]; - VkDeviceSize vertex_offsets[NV2A_VERTEXSHADER_ATTRIBUTES]; - int num_vertex_bindings; - - ReorderDrawMode draw_mode; - VkDeviceSize index_indirect_offset; - uint32_t draw_count; - uint32_t vertex_count; - int32_t first_vertex; - - bool has_dynamic_line_width; - float line_width; - - VkViewport viewport; - VkRect2D scissor; - - float push_values[NV2A_VERTEXSHADER_ATTRIBUTES * 4]; - int num_push_values; - bool use_push_constants; - bool use_bindless_textures; -#if OPT_BINDLESS_TEXTURES - uint32_t tex_bindless_indices[NV2A_MAX_TEXTURES]; -#endif - VkPipelineLayout layout; - - int sequence_number; - int group_order; - - bool color_write; - bool depth_test; - bool stencil_test; -} ReorderWindowEntry; - -#define REORDER_MAX_PIPELINES 32 - -typedef struct ReorderWindow { - ReorderWindowEntry entries[REORDER_WINDOW_MAX]; - int count; - bool active; - - PipelineBinding *seen_pipelines[REORDER_MAX_PIPELINES]; - int seen_pipeline_group[REORDER_MAX_PIPELINES]; - int num_seen_pipelines; - int next_group; -} ReorderWindow; -#endif - typedef struct QueryReport { QSIMPLEQ_ENTRY(QueryReport) entry; bool clear; @@ -462,16 +300,10 @@ typedef struct PGRAPHVkDisplayState { GLuint gl_texture_id; } PGRAPHVkDisplayState; -typedef enum { - COMPUTE_TYPE_DEPTH_STENCIL = 0, - COMPUTE_TYPE_DEPTH_STENCIL_DIRECT = 1, -} ComputeType; - typedef struct ComputePipelineKey { VkFormat host_fmt; bool pack; int workgroup_size; - ComputeType compute_type; } ComputePipelineKey; typedef struct ComputePipeline { @@ -488,13 +320,6 @@ typedef struct PGRAPHVkComputeState { VkPipelineLayout pipeline_layout; Lru pipeline_cache; ComputePipeline *pipeline_cache_entries; - - VkDescriptorPool direct_descriptor_pool; - VkDescriptorSetLayout direct_descriptor_set_layout; - VkDescriptorSet direct_descriptor_sets[1024]; - int direct_descriptor_set_index; - VkPipelineLayout direct_pipeline_layout; - VkSampler direct_depth_sampler; } PGRAPHVkComputeState; typedef struct PGRAPHVkState { @@ -505,11 +330,6 @@ typedef struct PGRAPHVkState { bool debug_utils_extension_enabled; bool custom_border_color_extension_enabled; bool memory_budget_extension_enabled; -#if OPT_BINDLESS_TEXTURES - bool bindless_textures_supported; - uint32_t tex_push_offset; - int max_vertex_push_attrs; -#endif VkPhysicalDevice physical_device; VkPhysicalDeviceFeatures enabled_physical_device_features; @@ -527,27 +347,14 @@ typedef struct PGRAPHVkState { VkFence command_buffer_fence; unsigned int command_buffer_start_time; bool in_command_buffer; -#if OPT_BINDLESS_TEXTURES - bool bindless_set_bound; -#endif uint32_t submit_count; VkCommandBuffer aux_command_buffer; bool in_aux_command_buffer; - VkFramebuffer framebuffers[MAX_FRAMEBUFFERS]; + VkFramebuffer framebuffers[50]; int framebuffer_index; bool framebuffer_dirty; - struct { - VkRenderPass render_pass; - VkImageView color_view; - VkImageView zeta_view; - uint32_t width; - uint32_t height; - VkFramebuffer framebuffer; - } fb_cache[FB_CACHE_MAX]; - int fb_cache_count; - VkFramebuffer current_framebuffer; VkRenderPass render_pass; GArray *render_passes; // RenderPass @@ -559,32 +366,11 @@ typedef struct PGRAPHVkState { PipelineBinding *pipeline_cache_entries; PipelineBinding *pipeline_binding; bool pipeline_binding_changed; - int64_t pipeline_cache_last_save_us; -#if OPT_ASYNC_COMPILE - bool async_draw_skip; - struct { - QemuMutex lock; - QemuCond cond; - QemuThread thread; - QSIMPLEQ_HEAD(, CompileJob) queue; - bool shutdown; - bool initialized; - int queue_depth; - } compile_worker; -#endif VkDescriptorPool descriptor_pool; VkDescriptorSetLayout descriptor_set_layout; - VkDescriptorSet *descriptor_sets; - int descriptor_set_count; + VkDescriptorSet descriptor_sets[1024]; int descriptor_set_index; -#if OPT_BINDLESS_TEXTURES - VkDescriptorPool bindless_descriptor_pool; - VkDescriptorSetLayout bindless_set_layout; - VkDescriptorSet bindless_descriptor_set; - uint64_t bindless_slot_bitmap[MAX_BINDLESS_TEXTURES / 64]; - uint32_t tex_bindless_indices[NV2A_MAX_TEXTURES]; -#endif StorageBuffer storage_buffers[BUFFER_COUNT]; PrimRewriteBuf prim_rewrite_buf; @@ -593,10 +379,6 @@ typedef struct PGRAPHVkState { size_t num_vertex_ram_buffer_syncs; unsigned long *uploaded_bitmap; size_t bitmap_size; - uint32_t pipeline_vertex_attr_gen; - uint32_t last_shader_state_gen; - uint32_t last_pipeline_state_gen; - uint32_t last_any_reg_gen; VkVertexInputAttributeDescription vertex_attribute_descriptions[NV2A_VERTEXSHADER_ATTRIBUTES]; int vertex_attribute_to_description_location[NV2A_VERTEXSHADER_ATTRIBUTES]; @@ -609,8 +391,6 @@ typedef struct PGRAPHVkState { QTAILQ_HEAD(, SurfaceBinding) surfaces; QTAILQ_HEAD(, SurfaceBinding) invalid_surfaces; QTAILQ_HEAD(, SurfaceBinding) shelved_surfaces; - QTAILQ_HEAD(, PooledSurfaceImage) surface_image_pool; - int surface_image_pool_count; SurfaceBinding *color_binding, *zeta_binding; bool downloads_pending; QemuEvent downloads_complete; @@ -622,9 +402,7 @@ typedef struct PGRAPHVkState { TextureBinding *texture_bindings[NV2A_MAX_TEXTURES]; bool tex_surface_direct[NV2A_MAX_TEXTURES]; VkImageView tex_surface_direct_views[NV2A_MAX_TEXTURES]; - VkImageLayout tex_surface_direct_layout[NV2A_MAX_TEXTURES]; TextureBinding dummy_texture; - uint32_t last_texture_state_gen; bool texture_bindings_changed; VkFormatProperties *texture_format_properties; @@ -635,19 +413,13 @@ typedef struct PGRAPHVkState { bool shader_bindings_changed; bool use_push_constants_for_uniform_attrs; -#if OPT_REORDER_SAFE_WINDOWS - ReorderWindow reorder_window; -#endif - Lru shader_module_cache; ShaderModuleCacheEntry *shader_module_cache_entries; - size_t shader_module_cache_target; // FIXME: Merge these into a structure uint64_t uniform_buffer_hashes[2]; size_t uniform_buffer_offsets[2]; bool uniforms_changed; - bool pre_draw_skipped; VkQueryPool query_pool; int max_queries_in_flight; // FIXME: Move out to constant @@ -746,8 +518,6 @@ VkDeviceSize pgraph_vk_update_vertex_inline_buffer(PGRAPHState *pg, void **data, void pgraph_vk_init_surfaces(PGRAPHState *pg); void pgraph_vk_finalize_surfaces(PGRAPHState *pg); void pgraph_vk_surface_flush(NV2AState *d); -void pgraph_vk_surface_image_pool_init(PGRAPHVkState *r); -void pgraph_vk_surface_image_pool_drain(PGRAPHVkState *r); void pgraph_vk_process_pending_downloads(NV2AState *d); void pgraph_vk_surface_download_if_dirty(NV2AState *d, SurfaceBinding *surface); SurfaceBinding *pgraph_vk_surface_get_within(NV2AState *d, hwaddr addr); @@ -772,14 +542,6 @@ void pgraph_vk_finalize_compute(PGRAPHState *pg); void pgraph_vk_pack_depth_stencil(PGRAPHState *pg, SurfaceBinding *surface, VkCommandBuffer cmd, VkBuffer src, VkBuffer dst, bool downscale); -void pgraph_vk_pack_depth_stencil_direct(PGRAPHState *pg, - SurfaceBinding *surface, - VkCommandBuffer cmd, - VkImageView depth_view, - VkBuffer stencil_buf, - VkDeviceSize stencil_offset, - VkDeviceSize stencil_size, - VkBuffer dst, bool downscale); void pgraph_vk_unpack_depth_stencil(PGRAPHState *pg, SurfaceBinding *surface, VkCommandBuffer cmd, VkBuffer src, VkBuffer dst); @@ -794,7 +556,6 @@ bool pgraph_vk_gl_external_memory_available(void); void pgraph_vk_init_textures(PGRAPHState *pg); void pgraph_vk_finalize_textures(PGRAPHState *pg); void pgraph_vk_bind_textures(NV2AState *d); -bool pgraph_vk_check_textures_fast_skip(PGRAPHState *pg); void pgraph_vk_mark_textures_possibly_dirty(NV2AState *d, hwaddr addr, hwaddr size); void pgraph_vk_trim_texture_cache(PGRAPHState *pg); @@ -804,11 +565,6 @@ void pgraph_vk_init_shaders(PGRAPHState *pg); void pgraph_vk_finalize_shaders(PGRAPHState *pg); void pgraph_vk_update_descriptor_sets(PGRAPHState *pg); void pgraph_vk_bind_shaders(PGRAPHState *pg); -#if OPT_ASYNC_COMPILE -void pgraph_vk_compile_worker_init(PGRAPHVkState *r); -void pgraph_vk_compile_worker_shutdown(PGRAPHVkState *r); -void pgraph_vk_compile_worker_enqueue(PGRAPHVkState *r, CompileJob *job); -#endif // reports.c void pgraph_vk_init_reports(PGRAPHState *pg); @@ -838,7 +594,6 @@ void pgraph_vk_draw_begin(NV2AState *d); void pgraph_vk_draw_end(NV2AState *d); void pgraph_vk_finish(PGRAPHState *pg, FinishReason why); void pgraph_vk_flush_draw(NV2AState *d); -void pgraph_vk_flush_reorder_window(NV2AState *d); void pgraph_vk_begin_command_buffer(PGRAPHState *pg); void pgraph_vk_ensure_command_buffer(PGRAPHState *pg); void pgraph_vk_ensure_not_in_render_pass(PGRAPHState *pg); diff --git a/hw/xbox/nv2a/pgraph/vk/shaders.c b/hw/xbox/nv2a/pgraph/vk/shaders.c index 2e9e83105b..d783c69204 100644 --- a/hw/xbox/nv2a/pgraph/vk/shaders.c +++ b/hw/xbox/nv2a/pgraph/vk/shaders.c @@ -22,10 +22,6 @@ #include "qemu/mstring.h" #include "renderer.h" -#if OPT_ASYNC_COMPILE -extern bool xemu_get_async_compile(void); -#endif - #define VSH_UBO_BINDING 0 #define PSH_UBO_BINDING 1 #define PSH_TEX_BINDING 2 @@ -36,29 +32,7 @@ static void create_descriptor_pool(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - size_t num_sets = r->descriptor_set_count; - -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - VkDescriptorPoolSize pool_sizes[] = { - { - .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 2 * num_sets, - }, - }; - - VkDescriptorPoolCreateInfo pool_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .poolSizeCount = ARRAY_SIZE(pool_sizes), - .pPoolSizes = pool_sizes, - .maxSets = num_sets, - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - }; - VK_CHECK(vkCreateDescriptorPool(r->device, &pool_info, NULL, - &r->descriptor_pool)); - return; - } -#endif + size_t num_sets = ARRAY_SIZE(r->descriptor_sets); VkDescriptorPoolSize pool_sizes[] = { { @@ -75,7 +49,7 @@ static void create_descriptor_pool(PGRAPHState *pg) .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = ARRAY_SIZE(pool_sizes), .pPoolSizes = pool_sizes, - .maxSets = num_sets, + .maxSets = ARRAY_SIZE(r->descriptor_sets), .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, }; VK_CHECK(vkCreateDescriptorPool(r->device, &pool_info, NULL, @@ -94,33 +68,6 @@ static void create_descriptor_set_layout(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - VkDescriptorSetLayoutBinding bindings[2] = { - { - .binding = VSH_UBO_BINDING, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, - }, - { - .binding = PSH_UBO_BINDING, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - }, - }; - VkDescriptorSetLayoutCreateInfo layout_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .bindingCount = ARRAY_SIZE(bindings), - .pBindings = bindings, - }; - VK_CHECK(vkCreateDescriptorSetLayout(r->device, &layout_info, NULL, - &r->descriptor_set_layout)); - return; - } -#endif - VkDescriptorSetLayoutBinding bindings[2 + NV2A_MAX_TEXTURES]; bindings[0] = (VkDescriptorSetLayoutBinding){ @@ -163,173 +110,47 @@ static void destroy_descriptor_set_layout(PGRAPHState *pg) static void create_descriptor_sets(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - int count = r->descriptor_set_count; - r->descriptor_sets = g_malloc_n(count, sizeof(VkDescriptorSet)); - - VkDescriptorSetLayout *layouts = - g_malloc_n(count, sizeof(VkDescriptorSetLayout)); - for (int i = 0; i < count; i++) { + VkDescriptorSetLayout layouts[ARRAY_SIZE(r->descriptor_sets)]; + for (int i = 0; i < ARRAY_SIZE(layouts); i++) { layouts[i] = r->descriptor_set_layout; } VkDescriptorSetAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = r->descriptor_pool, - .descriptorSetCount = count, + .descriptorSetCount = ARRAY_SIZE(r->descriptor_sets), .pSetLayouts = layouts, }; VK_CHECK( vkAllocateDescriptorSets(r->device, &alloc_info, r->descriptor_sets)); - g_free(layouts); } static void destroy_descriptor_sets(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - if (r->descriptor_sets == NULL) { - return; - } - vkFreeDescriptorSets(r->device, r->descriptor_pool, - r->descriptor_set_count, r->descriptor_sets); - g_free(r->descriptor_sets); - r->descriptor_sets = NULL; -} - -#if OPT_BINDLESS_TEXTURES -static void create_bindless_descriptor_resources(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - if (!r->bindless_textures_supported) { - return; + ARRAY_SIZE(r->descriptor_sets), r->descriptor_sets); + for (int i = 0; i < ARRAY_SIZE(r->descriptor_sets); i++) { + r->descriptor_sets[i] = VK_NULL_HANDLE; } - - VkDescriptorSetLayoutBinding bindings[3]; - VkDescriptorBindingFlags binding_flags[3]; - for (int i = 0; i < 3; i++) { - bindings[i] = (VkDescriptorSetLayoutBinding){ - .binding = (uint32_t)i, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = MAX_BINDLESS_TEXTURES, - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - }; - binding_flags[i] = - VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | - VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT; - } - - VkDescriptorSetLayoutBindingFlagsCreateInfo flags_info = { - .sType = - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, - .bindingCount = ARRAY_SIZE(binding_flags), - .pBindingFlags = binding_flags, - }; - VkDescriptorSetLayoutCreateInfo layout_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = &flags_info, - .flags = - VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, - .bindingCount = ARRAY_SIZE(bindings), - .pBindings = bindings, - }; - VK_CHECK(vkCreateDescriptorSetLayout(r->device, &layout_info, NULL, - &r->bindless_set_layout)); - - VkDescriptorPoolSize pool_size = { - .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 3 * MAX_BINDLESS_TEXTURES, - }; - VkDescriptorPoolCreateInfo pool_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, - .maxSets = 1, - .poolSizeCount = 1, - .pPoolSizes = &pool_size, - }; - VK_CHECK(vkCreateDescriptorPool(r->device, &pool_info, NULL, - &r->bindless_descriptor_pool)); - - VkDescriptorSetAllocateInfo alloc_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = r->bindless_descriptor_pool, - .descriptorSetCount = 1, - .pSetLayouts = &r->bindless_set_layout, - }; - VK_CHECK(vkAllocateDescriptorSets(r->device, &alloc_info, - &r->bindless_descriptor_set)); - - memset(r->bindless_slot_bitmap, 0, sizeof(r->bindless_slot_bitmap)); - memset(r->tex_bindless_indices, 0, sizeof(r->tex_bindless_indices)); - r->bindless_slot_bitmap[0] |= 1ULL; - for (uint32_t slot = BINDLESS_STAGE_SLOT_BASE; - slot < MAX_BINDLESS_TEXTURES; slot++) { - r->bindless_slot_bitmap[slot / 64] |= (1ULL << (slot % 64)); - } -} - -static void destroy_bindless_descriptor_resources(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - if (!r->bindless_textures_supported) { - return; - } - - vkDestroyDescriptorPool(r->device, r->bindless_descriptor_pool, NULL); - vkDestroyDescriptorSetLayout(r->device, r->bindless_set_layout, NULL); - r->bindless_descriptor_pool = VK_NULL_HANDLE; - r->bindless_set_layout = VK_NULL_HANDLE; - r->bindless_descriptor_set = VK_NULL_HANDLE; -} -#endif - -static bool can_use_vertex_push_constants(PGRAPHVkState *r, - const VshState *state) -{ - if (!r->use_push_constants_for_uniform_attrs) { - return false; - } -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported && - __builtin_popcount(state->uniform_attrs) > r->max_vertex_push_attrs) { - return false; - } -#endif - return true; } void pgraph_vk_update_descriptor_sets(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - ShaderBinding *binding = r->shader_binding; - - if (!binding) { - return; - } - -#if OPT_ASYNC_COMPILE - if (!qatomic_read(&binding->ready)) { - return; - } -#endif bool need_uniform_write = r->uniforms_changed || !r->storage_buffers[BUFFER_UNIFORM_STAGING].buffer_offset; - if (!(r->shader_bindings_changed || -#if OPT_BINDLESS_TEXTURES - (!r->bindless_textures_supported && r->texture_bindings_changed) || -#else - r->texture_bindings_changed || -#endif + if (!(r->shader_bindings_changed || r->texture_bindings_changed || (r->descriptor_set_index == 0) || need_uniform_write)) { return; // Nothing changed } + ShaderBinding *binding = r->shader_binding; ShaderUniformLayout *layouts[] = { &binding->vsh.module_info->uniforms, &binding->psh.module_info->uniforms }; VkDeviceSize ubo_buffer_total_size = 0; @@ -343,14 +164,16 @@ void pgraph_vk_update_descriptor_sets(PGRAPHState *pg) r->device_props.limits.minUniformBufferOffsetAlignment); bool need_descriptor_write_reset = - (r->descriptor_set_index >= r->descriptor_set_count); + (r->descriptor_set_index >= ARRAY_SIZE(r->descriptor_sets)); if (need_descriptor_write_reset || need_ubo_staging_buffer_reset) { pgraph_vk_finish(pg, VK_FINISH_REASON_NEED_BUFFER_SPACE); need_uniform_write = true; } - assert(r->descriptor_set_index < r->descriptor_set_count); + VkWriteDescriptorSet descriptor_writes[2 + NV2A_MAX_TEXTURES]; + + assert(r->descriptor_set_index < ARRAY_SIZE(r->descriptor_sets)); if (need_uniform_write) { for (int i = 0; i < ARRAY_SIZE(layouts); i++) { @@ -365,7 +188,6 @@ void pgraph_vk_update_descriptor_sets(PGRAPHState *pg) } VkDescriptorBufferInfo ubo_buffer_infos[2]; - VkWriteDescriptorSet descriptor_writes[2 + NV2A_MAX_TEXTURES]; for (int i = 0; i < ARRAY_SIZE(layouts); i++) { ubo_buffer_infos[i] = (VkDescriptorBufferInfo){ .buffer = r->storage_buffers[BUFFER_UNIFORM].buffer, @@ -383,38 +205,30 @@ void pgraph_vk_update_descriptor_sets(PGRAPHState *pg) }; } -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - vkUpdateDescriptorSets(r->device, 2, descriptor_writes, 0, NULL); - } else -#endif - { - VkDescriptorImageInfo image_infos[NV2A_MAX_TEXTURES]; - for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { - image_infos[i] = (VkDescriptorImageInfo){ - .imageLayout = r->tex_surface_direct[i] - ? r->tex_surface_direct_layout[i] - : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .imageView = r->tex_surface_direct[i] - ? r->tex_surface_direct_views[i] - : r->texture_bindings[i]->image_view, - .sampler = r->texture_bindings[i]->sampler, - }; - descriptor_writes[2 + i] = (VkWriteDescriptorSet){ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = r->descriptor_sets[r->descriptor_set_index], - .dstBinding = PSH_TEX_BINDING + i, - .dstArrayElement = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, - .pImageInfo = &image_infos[i], - }; - } - - vkUpdateDescriptorSets(r->device, 2 + NV2A_MAX_TEXTURES, - descriptor_writes, 0, NULL); + VkDescriptorImageInfo image_infos[NV2A_MAX_TEXTURES]; + for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { + image_infos[i] = (VkDescriptorImageInfo){ + .imageLayout = r->tex_surface_direct[i] + ? VK_IMAGE_LAYOUT_GENERAL + : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = r->tex_surface_direct[i] + ? r->tex_surface_direct_views[i] + : r->texture_bindings[i]->image_view, + .sampler = r->texture_bindings[i]->sampler, + }; + descriptor_writes[2 + i] = (VkWriteDescriptorSet){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = r->descriptor_sets[r->descriptor_set_index], + .dstBinding = PSH_TEX_BINDING + i, + .dstArrayElement = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .pImageInfo = &image_infos[i], + }; } + vkUpdateDescriptorSets(r->device, 6, descriptor_writes, 0, NULL); + r->descriptor_set_index++; } @@ -443,193 +257,47 @@ get_and_ref_shader_module_for_key(PGRAPHVkState *r, return module->module_info; } -#if OPT_ASYNC_COMPILE -static void shader_module_cache_add_pending_user(ShaderModuleCacheEntry *entry) -{ - if (entry) { - entry->pending_users++; - } -} - -static void shader_module_cache_remove_pending_user(ShaderModuleCacheEntry *entry) -{ - if (entry) { - assert(entry->pending_users > 0); - entry->pending_users--; - } -} - -static bool try_finalize_shader_binding(PGRAPHVkState *r, - ShaderBinding *binding) -{ - ShaderModuleCacheEntry *vsh_entry = binding->pending_vsh_entry; - ShaderModuleCacheEntry *geom_entry = binding->pending_geom_entry; - ShaderModuleCacheEntry *psh_entry = binding->pending_psh_entry; - - if (!vsh_entry || !psh_entry) { - return false; - } - if (!qatomic_read(&vsh_entry->ready)) { - return false; - } - if (geom_entry && !qatomic_read(&geom_entry->ready)) { - return false; - } - if (!qatomic_read(&psh_entry->ready)) { - return false; - } - - pgraph_vk_ref_shader_module(vsh_entry->module_info); - binding->vsh.module_info = vsh_entry->module_info; - if (geom_entry) { - pgraph_vk_ref_shader_module(geom_entry->module_info); - binding->geom.module_info = geom_entry->module_info; - } else { - binding->geom.module_info = NULL; - } - pgraph_vk_ref_shader_module(psh_entry->module_info); - binding->psh.module_info = psh_entry->module_info; - - update_shader_uniform_locs(binding); - - shader_module_cache_remove_pending_user(vsh_entry); - shader_module_cache_remove_pending_user(geom_entry); - shader_module_cache_remove_pending_user(psh_entry); - binding->pending_vsh_entry = NULL; - binding->pending_geom_entry = NULL; - binding->pending_psh_entry = NULL; - qatomic_set(&binding->ready, true); - return true; -} -#endif - static void shader_cache_entry_init(Lru *lru, LruNode *node, const void *state) { PGRAPHVkState *r = container_of(lru, PGRAPHVkState, shader_cache); ShaderBinding *binding = container_of(node, ShaderBinding, node); memcpy(&binding->state, state, sizeof(ShaderState)); -#if OPT_ASYNC_COMPILE - binding->pending_vsh_entry = NULL; - binding->pending_geom_entry = NULL; - binding->pending_psh_entry = NULL; - binding->ready = false; -#endif NV2A_VK_DPRINTF("cache miss"); nv2a_profile_inc_counter(NV2A_PROF_SHADER_GEN); + ShaderModuleCacheKey key; + bool need_geometry_shader = pgraph_glsl_need_geom(&binding->state.geom); - ShaderModuleCacheKey geom_key; if (need_geometry_shader) { - memset(&geom_key, 0, sizeof(geom_key)); - geom_key.kind = VK_SHADER_STAGE_GEOMETRY_BIT; - geom_key.geom.state = binding->state.geom; - geom_key.geom.glsl_opts.vulkan = true; + memset(&key, 0, sizeof(key)); + key.kind = VK_SHADER_STAGE_GEOMETRY_BIT; + key.geom.state = binding->state.geom; + key.geom.glsl_opts.vulkan = true; + binding->geom.module_info = get_and_ref_shader_module_for_key(r, &key); } else { binding->geom.module_info = NULL; } - ShaderModuleCacheKey vsh_key; - memset(&vsh_key, 0, sizeof(vsh_key)); - vsh_key.kind = VK_SHADER_STAGE_VERTEX_BIT; - vsh_key.vsh.state = binding->state.vsh; - vsh_key.vsh.glsl_opts.vulkan = true; - vsh_key.vsh.glsl_opts.prefix_outputs = need_geometry_shader; - vsh_key.vsh.glsl_opts.use_push_constants_for_uniform_attrs = - can_use_vertex_push_constants(r, &binding->state.vsh); - vsh_key.vsh.glsl_opts.ubo_binding = VSH_UBO_BINDING; -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - vsh_key.vsh.glsl_opts.ubo_set = 1; - if (vsh_key.vsh.glsl_opts.use_push_constants_for_uniform_attrs && - r->tex_push_offset == 0) { - vsh_key.vsh.glsl_opts.vertex_push_offset = - NV2A_MAX_TEXTURES * sizeof(uint32_t); - } - } -#endif + memset(&key, 0, sizeof(key)); + key.kind = VK_SHADER_STAGE_VERTEX_BIT; + key.vsh.state = binding->state.vsh; + key.vsh.glsl_opts.vulkan = true; + key.vsh.glsl_opts.prefix_outputs = need_geometry_shader; + key.vsh.glsl_opts.use_push_constants_for_uniform_attrs = + r->use_push_constants_for_uniform_attrs; + key.vsh.glsl_opts.ubo_binding = VSH_UBO_BINDING; + binding->vsh.module_info = get_and_ref_shader_module_for_key(r, &key); - ShaderModuleCacheKey psh_key; - memset(&psh_key, 0, sizeof(psh_key)); - psh_key.kind = VK_SHADER_STAGE_FRAGMENT_BIT; - psh_key.psh.state = binding->state.psh; - psh_key.psh.glsl_opts.vulkan = true; - psh_key.psh.glsl_opts.ubo_binding = PSH_UBO_BINDING; -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - psh_key.psh.glsl_opts.ubo_set = 1; - psh_key.psh.glsl_opts.bindless = true; - psh_key.psh.glsl_opts.tex_push_offset = r->tex_push_offset; - } else -#endif - { - psh_key.psh.glsl_opts.tex_binding = PSH_TEX_BINDING; - } + memset(&key, 0, sizeof(key)); + key.kind = VK_SHADER_STAGE_FRAGMENT_BIT; + key.psh.state = binding->state.psh; + key.psh.glsl_opts.vulkan = true; + key.psh.glsl_opts.ubo_binding = PSH_UBO_BINDING; + key.psh.glsl_opts.tex_binding = PSH_TEX_BINDING; + binding->psh.module_info = get_and_ref_shader_module_for_key(r, &key); -#if OPT_ASYNC_COMPILE - if (xemu_get_async_compile()) { - uint64_t vsh_hash = fast_hash((void *)&vsh_key, sizeof(vsh_key)); - LruNode *vsh_node = lru_lookup(&r->shader_module_cache, vsh_hash, - &vsh_key); - ShaderModuleCacheEntry *vsh_entry = - container_of(vsh_node, ShaderModuleCacheEntry, node); - ShaderModuleCacheEntry *geom_entry = NULL; - if (need_geometry_shader) { - uint64_t geom_hash = fast_hash((void *)&geom_key, - sizeof(geom_key)); - LruNode *geom_node = lru_lookup(&r->shader_module_cache, geom_hash, - &geom_key); - geom_entry = container_of(geom_node, ShaderModuleCacheEntry, node); - } - uint64_t psh_hash = fast_hash((void *)&psh_key, sizeof(psh_key)); - LruNode *psh_node = lru_lookup(&r->shader_module_cache, psh_hash, - &psh_key); - ShaderModuleCacheEntry *psh_entry = - container_of(psh_node, ShaderModuleCacheEntry, node); - - bool all_ready = qatomic_read(&vsh_entry->ready) && - (!geom_entry || qatomic_read(&geom_entry->ready)) && - qatomic_read(&psh_entry->ready); - - if (all_ready) { - pgraph_vk_ref_shader_module(vsh_entry->module_info); - binding->vsh.module_info = vsh_entry->module_info; - if (geom_entry) { - pgraph_vk_ref_shader_module(geom_entry->module_info); - binding->geom.module_info = geom_entry->module_info; - } else { - binding->geom.module_info = NULL; - } - pgraph_vk_ref_shader_module(psh_entry->module_info); - binding->psh.module_info = psh_entry->module_info; - update_shader_uniform_locs(binding); - qatomic_set(&binding->ready, true); - } else { - binding->vsh.module_info = NULL; - binding->geom.module_info = NULL; - binding->psh.module_info = NULL; - binding->pending_vsh_entry = vsh_entry; - binding->pending_geom_entry = geom_entry; - binding->pending_psh_entry = psh_entry; - shader_module_cache_add_pending_user(vsh_entry); - shader_module_cache_add_pending_user(geom_entry); - shader_module_cache_add_pending_user(psh_entry); - qatomic_set(&binding->ready, false); - } - return; - } -#endif - - if (need_geometry_shader) { - binding->geom.module_info = get_and_ref_shader_module_for_key( - r, &geom_key); - } - binding->vsh.module_info = get_and_ref_shader_module_for_key(r, &vsh_key); - binding->psh.module_info = get_and_ref_shader_module_for_key(r, &psh_key); update_shader_uniform_locs(binding); -#if OPT_ASYNC_COMPILE - qatomic_set(&binding->ready, true); -#endif } static void shader_cache_entry_post_evict(Lru *lru, LruNode *node) @@ -637,15 +305,6 @@ static void shader_cache_entry_post_evict(Lru *lru, LruNode *node) PGRAPHVkState *r = container_of(lru, PGRAPHVkState, shader_cache); ShaderBinding *snode = container_of(node, ShaderBinding, node); -#if OPT_ASYNC_COMPILE - shader_module_cache_remove_pending_user(snode->pending_vsh_entry); - shader_module_cache_remove_pending_user(snode->pending_geom_entry); - shader_module_cache_remove_pending_user(snode->pending_psh_entry); - snode->pending_vsh_entry = NULL; - snode->pending_geom_entry = NULL; - snode->pending_psh_entry = NULL; -#endif - ShaderModuleInfo *modules[] = { snode->vsh.module_info, snode->geom.module_info, @@ -672,22 +331,6 @@ static void shader_module_cache_entry_init(Lru *lru, LruNode *node, container_of(node, ShaderModuleCacheEntry, node); memcpy(&module->key, key, sizeof(ShaderModuleCacheKey)); -#if OPT_ASYNC_COMPILE - module->pending_users = 0; - if (xemu_get_async_compile()) { - module->module_info = NULL; - qatomic_set(&module->ready, false); - - CompileJob *job = g_malloc0(sizeof(*job)); - job->type = COMPILE_JOB_SHADER_MODULE; - job->shader_module.target = module; - memcpy(&job->shader_module.key, key, sizeof(ShaderModuleCacheKey)); - pgraph_vk_compile_worker_enqueue(r, job); - return; - } - qatomic_set(&module->ready, true); -#endif - MString *code; switch (module->key.kind) { @@ -714,21 +357,6 @@ static void shader_module_cache_entry_init(Lru *lru, LruNode *node, mstring_unref(code); } -#if OPT_ASYNC_COMPILE -static bool shader_module_cache_pre_evict(Lru *lru, LruNode *node) -{ - ShaderModuleCacheEntry *module = - container_of(node, ShaderModuleCacheEntry, node); - return qatomic_read(&module->ready) && module->pending_users == 0; -} - -static bool shader_cache_pre_evict(Lru *lru, LruNode *node) -{ - ShaderBinding *binding = container_of(node, ShaderBinding, node); - return qatomic_read(&binding->ready); -} -#endif - static void shader_module_cache_entry_post_evict(Lru *lru, LruNode *node) { PGRAPHVkState *r = container_of(lru, PGRAPHVkState, shader_module_cache); @@ -751,7 +379,7 @@ static void shader_cache_init(PGRAPHState *pg) PGRAPHVkState *r = pg->vk_renderer_state; const size_t shader_cache_size = 1024; - lru_init(&r->shader_cache, 2048); + lru_init(&r->shader_cache, 1u << 16); r->shader_cache_entries = g_malloc_n(shader_cache_size, sizeof(ShaderBinding)); assert(r->shader_cache_entries != NULL); for (int i = 0; i < shader_cache_size; i++) { @@ -760,21 +388,10 @@ static void shader_cache_init(PGRAPHState *pg) r->shader_cache.init_node = shader_cache_entry_init; r->shader_cache.compare_nodes = shader_cache_entry_compare; r->shader_cache.post_node_evict = shader_cache_entry_post_evict; -#if OPT_ASYNC_COMPILE - r->shader_cache.pre_node_evict = shader_cache_pre_evict; -#endif - const size_t shader_module_cache_size = - r->shader_module_cache_target ? r->shader_module_cache_target - : 50 * 1024; - size_t shader_module_hash_buckets = shader_module_cache_size * 2; - if (shader_module_hash_buckets < 4096) { - shader_module_hash_buckets = 4096; - } - if (shader_module_hash_buckets > (1 << 16)) { - shader_module_hash_buckets = 1 << 16; - } - lru_init(&r->shader_module_cache, shader_module_hash_buckets); + /* FIXME: Make this configurable */ + const size_t shader_module_cache_size = 50 * 1024; + lru_init(&r->shader_module_cache, 1u << 16); r->shader_module_cache_entries = g_malloc_n(shader_module_cache_size, sizeof(ShaderModuleCacheEntry)); assert(r->shader_module_cache_entries != NULL); @@ -787,39 +404,17 @@ static void shader_cache_init(PGRAPHState *pg) r->shader_module_cache.compare_nodes = shader_module_cache_entry_compare; r->shader_module_cache.post_node_evict = shader_module_cache_entry_post_evict; -#if OPT_ASYNC_COMPILE - r->shader_module_cache.pre_node_evict = shader_module_cache_pre_evict; -#endif } -#if OPT_ASYNC_COMPILE -static void finalize_pending_shader_binding(Lru *lru, LruNode *node, - void *opaque) -{ - PGRAPHVkState *r = opaque; - ShaderBinding *binding = container_of(node, ShaderBinding, node); - - if (!qatomic_read(&binding->ready)) { - try_finalize_shader_binding(r, binding); - } -} -#endif - static void shader_cache_finalize(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; -#if OPT_ASYNC_COMPILE - lru_visit_active(&r->shader_cache, finalize_pending_shader_binding, r); -#endif - lru_flush(&r->shader_cache); - lru_destroy(&r->shader_cache); g_free(r->shader_cache_entries); r->shader_cache_entries = NULL; lru_flush(&r->shader_module_cache); - lru_destroy(&r->shader_module_cache); g_free(r->shader_module_cache_entries); r->shader_module_cache_entries = NULL; } @@ -856,14 +451,6 @@ static void update_shader_uniforms(PGRAPHState *pg) assert(r->shader_binding); ShaderBinding *binding = r->shader_binding; - -#if OPT_ASYNC_COMPILE - if (!qatomic_read(&binding->ready)) { - NV2A_VK_DGROUP_END(); - return; - } -#endif - ShaderUniformLayout *layouts[] = { &binding->vsh.module_info->uniforms, &binding->psh.module_info->uniforms }; @@ -929,15 +516,6 @@ void pgraph_vk_bind_shaders(PGRAPHState *pg) nv2a_profile_inc_counter(NV2A_PROF_SHADER_BIND_NOTDIRTY); } -#if OPT_ASYNC_COMPILE - if (r->shader_binding && !qatomic_read(&r->shader_binding->ready)) { - if (try_finalize_shader_binding(r, r->shader_binding)) { - r->shader_bindings_changed = true; - r->uniforms_changed = true; - } - } -#endif - update_shader_uniforms(pg); NV2A_VK_DGROUP_END(); @@ -947,48 +525,20 @@ void pgraph_vk_init_shaders(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; - r->descriptor_set_count = NUM_GFX_DESCRIPTOR_SETS; pgraph_vk_init_glsl_compiler(); create_descriptor_pool(pg); create_descriptor_set_layout(pg); create_descriptor_sets(pg); -#if OPT_BINDLESS_TEXTURES - create_bindless_descriptor_resources(pg); -#endif -#if OPT_ASYNC_COMPILE - if (xemu_get_async_compile()) { - pgraph_vk_compile_worker_init(r); - } -#endif shader_cache_init(pg); -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - size_t vtx_budget = r->max_vertex_push_attrs * 4 * sizeof(float); - r->use_push_constants_for_uniform_attrs = - (r->device_props.limits.maxPushConstantsSize >= - vtx_budget + NV2A_MAX_TEXTURES * sizeof(uint32_t)); - } else -#endif - { r->use_push_constants_for_uniform_attrs = (r->device_props.limits.maxPushConstantsSize >= MAX_UNIFORM_ATTR_VALUES_SIZE); - } } void pgraph_vk_finalize_shaders(PGRAPHState *pg) { - PGRAPHVkState *r = pg->vk_renderer_state; - -#if OPT_ASYNC_COMPILE - pgraph_vk_compile_worker_shutdown(r); -#endif - shader_cache_finalize(pg); -#if OPT_BINDLESS_TEXTURES - destroy_bindless_descriptor_resources(pg); -#endif destroy_descriptor_sets(pg); destroy_descriptor_set_layout(pg); destroy_descriptor_pool(pg); diff --git a/hw/xbox/nv2a/pgraph/vk/surface-compute.c b/hw/xbox/nv2a/pgraph/vk/surface-compute.c index 26552de47f..ecdc4c30c4 100644 --- a/hw/xbox/nv2a/pgraph/vk/surface-compute.c +++ b/hw/xbox/nv2a/pgraph/vk/surface-compute.c @@ -118,25 +118,6 @@ const char *unpack_z24s8_to_d32_sfloat_s8_uint_glsl = " }\n" "}\n"; -static const char *pack_depth_stencil_direct_glsl = - "layout(push_constant) uniform PushConstants { uint width_in, width_out; };\n" - "layout(set = 0, binding = 0) uniform sampler2D depth_tex;\n" - "layout(set = 0, binding = 1) readonly buffer StencilIn { uint stencil_in[]; };\n" - "layout(set = 0, binding = 2) writeonly buffer PackedOut { uint packed_out[]; };\n" - "void main() {\n" - " uint idx_out = gl_GlobalInvocationID.x;\n" - " uint scale = width_in / width_out;\n" - " uint out_x = idx_out % width_out;\n" - " uint out_y = idx_out / width_out;\n" - " uint in_x = out_x * scale;\n" - " uint in_y = out_y * scale;\n" - " uint idx_in = in_y * width_in + in_x;\n" - " float depth = texelFetch(depth_tex, ivec2(in_x, in_y), 0).r;\n" - " uint depth_value = uint(depth * float(0xFFFFFF));\n" - " uint stencil_value = (stencil_in[idx_in / 4] >> ((idx_in % 4) * 8)) & 0xFFu;\n" - " packed_out[idx_out] = depth_value << 8 | stencil_value;\n" - "}\n"; - static gchar *get_compute_shader_glsl(VkFormat host_fmt, bool pack, int workgroup_size) { @@ -188,32 +169,6 @@ static void create_descriptor_pool(PGRAPHState *pg) &r->compute.descriptor_pool)); } -static void create_direct_descriptor_pool(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - VkDescriptorPoolSize pool_sizes[] = { - { - .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = ARRAY_SIZE(r->compute.direct_descriptor_sets), - }, - { - .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 2 * ARRAY_SIZE(r->compute.direct_descriptor_sets), - }, - }; - - VkDescriptorPoolCreateInfo pool_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .poolSizeCount = ARRAY_SIZE(pool_sizes), - .pPoolSizes = pool_sizes, - .maxSets = ARRAY_SIZE(r->compute.direct_descriptor_sets), - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - }; - VK_CHECK(vkCreateDescriptorPool(r->device, &pool_info, NULL, - &r->compute.direct_descriptor_pool)); -} - static void destroy_descriptor_pool(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -222,14 +177,6 @@ static void destroy_descriptor_pool(PGRAPHState *pg) r->compute.descriptor_pool = VK_NULL_HANDLE; } -static void destroy_direct_descriptor_pool(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - vkDestroyDescriptorPool(r->device, r->compute.direct_descriptor_pool, NULL); - r->compute.direct_descriptor_pool = VK_NULL_HANDLE; -} - static void create_descriptor_set_layout(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -254,40 +201,6 @@ static void create_descriptor_set_layout(PGRAPHState *pg) &r->compute.descriptor_set_layout)); } -static void create_direct_descriptor_set_layout(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - VkDescriptorSetLayoutBinding bindings[] = { - { - .binding = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - { - .binding = 1, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - { - .binding = 2, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - }; - - VkDescriptorSetLayoutCreateInfo layout_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .bindingCount = ARRAY_SIZE(bindings), - .pBindings = bindings, - }; - VK_CHECK(vkCreateDescriptorSetLayout( - r->device, &layout_info, NULL, &r->compute.direct_descriptor_set_layout)); -} - static void destroy_descriptor_set_layout(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -297,16 +210,6 @@ static void destroy_descriptor_set_layout(PGRAPHState *pg) r->compute.descriptor_set_layout = VK_NULL_HANDLE; } -static void destroy_direct_descriptor_set_layout(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - vkDestroyDescriptorSetLayout(r->device, - r->compute.direct_descriptor_set_layout, - NULL); - r->compute.direct_descriptor_set_layout = VK_NULL_HANDLE; -} - static void create_descriptor_sets(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -325,24 +228,6 @@ static void create_descriptor_sets(PGRAPHState *pg) r->compute.descriptor_sets)); } -static void create_direct_descriptor_sets(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - VkDescriptorSetLayout layouts[ARRAY_SIZE(r->compute.direct_descriptor_sets)]; - for (int i = 0; i < ARRAY_SIZE(layouts); i++) { - layouts[i] = r->compute.direct_descriptor_set_layout; - } - VkDescriptorSetAllocateInfo alloc_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = r->compute.direct_descriptor_pool, - .descriptorSetCount = ARRAY_SIZE(r->compute.direct_descriptor_sets), - .pSetLayouts = layouts, - }; - VK_CHECK(vkAllocateDescriptorSets(r->device, &alloc_info, - r->compute.direct_descriptor_sets)); -} - static void destroy_descriptor_sets(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -355,18 +240,6 @@ static void destroy_descriptor_sets(PGRAPHState *pg) } } -static void destroy_direct_descriptor_sets(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - vkFreeDescriptorSets(r->device, r->compute.direct_descriptor_pool, - ARRAY_SIZE(r->compute.direct_descriptor_sets), - r->compute.direct_descriptor_sets); - for (int i = 0; i < ARRAY_SIZE(r->compute.direct_descriptor_sets); i++) { - r->compute.direct_descriptor_sets[i] = VK_NULL_HANDLE; - } -} - static void create_compute_pipeline_layout(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -386,74 +259,20 @@ static void create_compute_pipeline_layout(PGRAPHState *pg) &r->compute.pipeline_layout)); } -static void create_direct_compute_pipeline_layout(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - VkPushConstantRange push_constant_range = { - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .size = 2 * sizeof(uint32_t), - }; - VkPipelineLayoutCreateInfo pipeline_layout_info = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 1, - .pSetLayouts = &r->compute.direct_descriptor_set_layout, - .pushConstantRangeCount = 1, - .pPushConstantRanges = &push_constant_range, - }; - VK_CHECK(vkCreatePipelineLayout( - r->device, &pipeline_layout_info, NULL, &r->compute.direct_pipeline_layout)); -} - static void destroy_compute_pipeline_layout(PGRAPHVkState *r) { vkDestroyPipelineLayout(r->device, r->compute.pipeline_layout, NULL); r->compute.pipeline_layout = VK_NULL_HANDLE; } -static void destroy_direct_compute_pipeline_layout(PGRAPHVkState *r) -{ - vkDestroyPipelineLayout(r->device, r->compute.direct_pipeline_layout, NULL); - r->compute.direct_pipeline_layout = VK_NULL_HANDLE; -} - -static void create_direct_depth_sampler(PGRAPHVkState *r) -{ - VkSamplerCreateInfo sampler_info = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .magFilter = VK_FILTER_NEAREST, - .minFilter = VK_FILTER_NEAREST, - .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .anisotropyEnable = VK_FALSE, - .compareEnable = VK_FALSE, - .compareOp = VK_COMPARE_OP_ALWAYS, - .minLod = 0.0f, - .maxLod = 0.0f, - .maxAnisotropy = 1.0f, - .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, - }; - VK_CHECK(vkCreateSampler(r->device, &sampler_info, NULL, - &r->compute.direct_depth_sampler)); -} - -static void destroy_direct_depth_sampler(PGRAPHVkState *r) -{ - vkDestroySampler(r->device, r->compute.direct_depth_sampler, NULL); - r->compute.direct_depth_sampler = VK_NULL_HANDLE; -} - -static VkPipeline create_compute_pipeline(PGRAPHVkState *r, const char *glsl, - VkPipelineLayout layout) +static VkPipeline create_compute_pipeline(PGRAPHVkState *r, const char *glsl) { ShaderModuleInfo *module = pgraph_vk_create_shader_module_from_glsl( r, VK_SHADER_STAGE_COMPUTE_BIT, glsl); VkComputePipelineCreateInfo pipeline_info = { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - .layout = layout, + .layout = r->compute.pipeline_layout, .stage = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, @@ -504,17 +323,13 @@ bool pgraph_vk_compute_needs_finish(PGRAPHVkState *r) { bool need_descriptor_write_reset = (r->compute.descriptor_set_index >= ARRAY_SIZE(r->compute.descriptor_sets)); - bool need_direct_descriptor_write_reset = - (r->compute.direct_descriptor_set_index >= - ARRAY_SIZE(r->compute.direct_descriptor_sets)); - return need_descriptor_write_reset || need_direct_descriptor_write_reset; + return need_descriptor_write_reset; } void pgraph_vk_compute_finish_complete(PGRAPHVkState *r) { r->compute.descriptor_set_index = 0; - r->compute.direct_descriptor_set_index = 0; } static int get_workgroup_size_for_output_units(PGRAPHVkState *r, int output_units) @@ -548,7 +363,6 @@ static ComputePipeline *get_compute_pipeline(PGRAPHVkState *r, VkFormat host_fmt key.host_fmt = host_fmt; key.pack = pack; key.workgroup_size = workgroup_size; - key.compute_type = COMPUTE_TYPE_DEPTH_STENCIL; LruNode *node = lru_lookup(&r->compute.pipeline_cache, fast_hash((void *)&key, sizeof(key)), &key); @@ -711,107 +525,6 @@ void pgraph_vk_unpack_depth_stencil(PGRAPHState *pg, SurfaceBinding *surface, pgraph_vk_end_debug_marker(r, cmd); } -void pgraph_vk_pack_depth_stencil_direct(PGRAPHState *pg, - SurfaceBinding *surface, - VkCommandBuffer cmd, - VkImageView depth_view, - VkBuffer stencil_buf, - VkDeviceSize stencil_offset, - VkDeviceSize stencil_size, - VkBuffer dst, bool downscale) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - unsigned int input_width = surface->width, input_height = surface->height; - pgraph_apply_scaling_factor(pg, &input_width, &input_height); - - unsigned int output_width = surface->width, output_height = surface->height; - if (!downscale) { - pgraph_apply_scaling_factor(pg, &output_width, &output_height); - } - - size_t output_size = output_width * output_height * 4; - - assert(r->compute.direct_descriptor_set_index < - ARRAY_SIZE(r->compute.direct_descriptor_sets)); - - int ds_idx = r->compute.direct_descriptor_set_index++; - VkDescriptorSet ds = r->compute.direct_descriptor_sets[ds_idx]; - - VkDescriptorImageInfo image_info = { - .imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, - .imageView = depth_view, - .sampler = r->compute.direct_depth_sampler, - }; - VkDescriptorBufferInfo stencil_buf_info = { - .buffer = stencil_buf, - .offset = stencil_offset, - .range = stencil_size, - }; - VkDescriptorBufferInfo output_buf_info = { - .buffer = dst, - .offset = 0, - .range = output_size, - }; - VkWriteDescriptorSet writes[] = { - { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = ds, - .dstBinding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, - .pImageInfo = &image_info, - }, - { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = ds, - .dstBinding = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .pBufferInfo = &stencil_buf_info, - }, - { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = ds, - .dstBinding = 2, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .pBufferInfo = &output_buf_info, - }, - }; - vkUpdateDescriptorSets(r->device, ARRAY_SIZE(writes), writes, 0, NULL); - - size_t output_units = output_width * output_height; - int workgroup_size = get_workgroup_size_for_output_units(r, output_units); - - ComputePipelineKey key; - memset(&key, 0, sizeof(key)); - key.workgroup_size = workgroup_size; - key.compute_type = COMPUTE_TYPE_DEPTH_STENCIL_DIRECT; - - LruNode *node = lru_lookup(&r->compute.pipeline_cache, - fast_hash((void *)&key, sizeof(key)), &key); - ComputePipeline *pipeline = container_of(node, ComputePipeline, node); - assert(pipeline); - - size_t group_count = output_units / workgroup_size; - - pgraph_vk_begin_debug_marker(r, cmd, RGBA_PINK, __func__); - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline); - vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, - r->compute.direct_pipeline_layout, 0, 1, &ds, 0, - NULL); - - uint32_t push_constants[2] = { input_width, output_width }; - assert(sizeof(push_constants) == 8); - vkCmdPushConstants(cmd, r->compute.direct_pipeline_layout, - VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants), - push_constants); - - vkCmdDispatch(cmd, group_count, 1, 1); - pgraph_vk_end_debug_marker(r, cmd); -} - static void pipeline_cache_entry_init(Lru *lru, LruNode *node, const void *state) { @@ -825,25 +538,10 @@ static void pipeline_cache_entry_init(Lru *lru, LruNode *node, "Warning: Needed compute shader with workgroup size = 1\n"); } - gchar *glsl; - VkPipelineLayout layout; - switch (snode->key.compute_type) { - case COMPUTE_TYPE_DEPTH_STENCIL_DIRECT: - glsl = g_strdup_printf( - "#version 450\n" - "layout(local_size_x = %d, local_size_y = 1, local_size_z = 1) in;\n" - "%s", snode->key.workgroup_size, pack_depth_stencil_direct_glsl); - layout = r->compute.direct_pipeline_layout; - break; - case COMPUTE_TYPE_DEPTH_STENCIL: - default: - glsl = get_compute_shader_glsl( - snode->key.host_fmt, snode->key.pack, snode->key.workgroup_size); - layout = r->compute.pipeline_layout; - break; - } + gchar *glsl = get_compute_shader_glsl( + snode->key.host_fmt, snode->key.pack, snode->key.workgroup_size); assert(glsl); - snode->pipeline = create_compute_pipeline(r, glsl, layout); + snode->pipeline = create_compute_pipeline(r, glsl); g_free(glsl); } @@ -869,8 +567,8 @@ static bool pipeline_cache_entry_compare(Lru *lru, LruNode *node, static void pipeline_cache_init(PGRAPHVkState *r) { - const size_t pipeline_cache_size = 100; - lru_init(&r->compute.pipeline_cache, 256); + const size_t pipeline_cache_size = 100; // FIXME: Trim + lru_init(&r->compute.pipeline_cache, 1u << 16); r->compute.pipeline_cache_entries = g_malloc_n(pipeline_cache_size, sizeof(ComputePipeline)); assert(r->compute.pipeline_cache_entries != NULL); for (int i = 0; i < pipeline_cache_size; i++) { @@ -884,7 +582,6 @@ static void pipeline_cache_init(PGRAPHVkState *r) static void pipeline_cache_finalize(PGRAPHVkState *r) { lru_flush(&r->compute.pipeline_cache); - lru_destroy(&r->compute.pipeline_cache); g_free(r->compute.pipeline_cache_entries); r->compute.pipeline_cache_entries = NULL; } @@ -897,11 +594,6 @@ void pgraph_vk_init_compute(PGRAPHState *pg) create_descriptor_set_layout(pg); create_descriptor_sets(pg); create_compute_pipeline_layout(pg); - create_direct_descriptor_pool(pg); - create_direct_descriptor_set_layout(pg); - create_direct_descriptor_sets(pg); - create_direct_compute_pipeline_layout(pg); - create_direct_depth_sampler(r); pipeline_cache_init(r); } @@ -912,13 +604,8 @@ void pgraph_vk_finalize_compute(PGRAPHState *pg) assert(!r->in_command_buffer); pipeline_cache_finalize(r); - destroy_direct_depth_sampler(r); - destroy_direct_compute_pipeline_layout(r); destroy_compute_pipeline_layout(r); - destroy_direct_descriptor_sets(pg); destroy_descriptor_sets(pg); - destroy_direct_descriptor_set_layout(pg); destroy_descriptor_set_layout(pg); - destroy_direct_descriptor_pool(pg); destroy_descriptor_pool(pg); } diff --git a/hw/xbox/nv2a/pgraph/vk/surface.c b/hw/xbox/nv2a/pgraph/vk/surface.c index 58dfbf869b..e6466adb49 100644 --- a/hw/xbox/nv2a/pgraph/vk/surface.c +++ b/hw/xbox/nv2a/pgraph/vk/surface.c @@ -76,11 +76,7 @@ unsigned int pgraph_vk_get_surface_scale_factor(NV2AState *d) void pgraph_vk_reload_surface_scale_factor(PGRAPHState *pg) { int factor = g_config.display.quality.surface_scale; - int new_factor = MAX(factor, 1); - if (pg->surface_scale_factor != new_factor) { - pg->shader_state_gen++; - } - pg->surface_scale_factor = new_factor; + pg->surface_scale_factor = MAX(factor, 1); } // FIXME: Move to common @@ -151,142 +147,6 @@ void pgraph_vk_download_surfaces_in_range_if_dirty(PGRAPHState *pg, } } -#ifdef __ANDROID__ -static uint8_t android_vk_surface_expand_5_to_8(uint8_t value) -{ - return (value << 3) | (value >> 2); -} - -static bool android_vk_surface_uses_host_conversion(const SurfaceBinding *surface) -{ - if (!surface || !surface->color) { - return false; - } - - switch (surface->shape.color_format) { - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_O1R5G5B5: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_O8R8G8B8: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: - return true; - default: - return false; - } -} - -static void android_vk_surface_convert_to_host(const SurfaceBinding *surface, - const uint8_t *src, - unsigned int width, - unsigned int height, - unsigned int src_stride, - uint8_t *dst, - unsigned int dst_stride) -{ - unsigned int x, y; - - switch (surface->shape.color_format) { - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_O1R5G5B5: - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * dst_stride; - - switch (surface->host_fmt.vk_format) { - case VK_FORMAT_A1R5G5B5_UNORM_PACK16: - for (x = 0; x < width; x++) { - uint16_t pixel = lduw_le_p(src_row + x * 2) | 0x8000; - stw_le_p(dst_row + x * 2, pixel); - } - break; - case VK_FORMAT_R5G6B5_UNORM_PACK16: - for (x = 0; x < width; x++) { - uint16_t pixel = lduw_le_p(src_row + x * 2); - uint16_t r5 = (pixel >> 10) & 0x1F; - uint16_t g5 = (pixel >> 5) & 0x1F; - uint16_t b5 = pixel & 0x1F; - uint16_t packed = - (r5 << 11) | (((g5 << 1) | (g5 >> 4)) << 5) | b5; - stw_le_p(dst_row + x * 2, packed); - } - break; - case VK_FORMAT_B8G8R8A8_UNORM: - case VK_FORMAT_R8G8B8A8_UNORM: - for (x = 0; x < width; x++) { - uint16_t pixel = lduw_le_p(src_row + x * 2); - uint8_t r = - android_vk_surface_expand_5_to_8((pixel >> 10) & 0x1F); - uint8_t g = - android_vk_surface_expand_5_to_8((pixel >> 5) & 0x1F); - uint8_t b = android_vk_surface_expand_5_to_8(pixel & 0x1F); - uint8_t *out = dst_row + x * 4; - - if (surface->host_fmt.vk_format == - VK_FORMAT_B8G8R8A8_UNORM) { - out[0] = b; - out[1] = g; - out[2] = r; - out[3] = 0xFF; - } else { - out[0] = r; - out[1] = g; - out[2] = b; - out[3] = 0xFF; - } - } - break; - default: - g_assert_not_reached(); - } - } - break; - - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_O8R8G8B8: - case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: - { - bool preserve_alpha = - surface->shape.color_format == - NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8; - - for (y = 0; y < height; y++) { - const uint8_t *src_row = src + y * src_stride; - uint8_t *dst_row = dst + y * dst_stride; - - switch (surface->host_fmt.vk_format) { - case VK_FORMAT_B8G8R8A8_UNORM: - for (x = 0; x < width; x++) { - const uint8_t *pixel = src_row + x * 4; - uint8_t *out = dst_row + x * 4; - out[0] = pixel[0]; - out[1] = pixel[1]; - out[2] = pixel[2]; - out[3] = preserve_alpha ? pixel[3] : 0xFF; - } - break; - case VK_FORMAT_R8G8B8A8_UNORM: - for (x = 0; x < width; x++) { - const uint8_t *pixel = src_row + x * 4; - uint8_t *out = dst_row + x * 4; - out[0] = pixel[2]; - out[1] = pixel[1]; - out[2] = pixel[0]; - out[3] = preserve_alpha ? pixel[3] : 0xFF; - } - break; - default: - g_assert_not_reached(); - } - } - break; - } - - default: - g_assert_not_reached(); - } -} -#endif - static void download_surface_to_buffer(NV2AState *d, SurfaceBinding *surface, uint8_t *pixels) { @@ -347,9 +207,9 @@ static void download_surface_to_buffer(NV2AState *d, SurfaceBinding *surface, pgraph_vk_transition_image_layout( pg, cmd, surface->image, surface->host_fmt.vk_format, - surface->image_layout, + surface->color ? VK_IMAGE_LAYOUT_GENERAL : + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; int num_copy_regions = 1; VkBufferImageCopy copy_regions[2]; @@ -459,9 +319,6 @@ static void download_surface_to_buffer(NV2AState *d, SurfaceBinding *surface, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, surface->color ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - surface->image_layout = - surface->color ? VK_IMAGE_LAYOUT_GENERAL : - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // FIXME: Verify output of depth stencil conversion // FIXME: Track current layout and only transition when required @@ -624,11 +481,6 @@ static void download_surface(NV2AState *d, SurfaceBinding *surface, bool force) return; } - if (!surface->draw_dirty && - surface->download_generation == surface->draw_generation) { - return; - } - // FIXME: Respect write enable at last TOU? download_surface_to_buffer(d, surface, d->vram_ptr + surface->vram_addr); @@ -639,13 +491,9 @@ static void download_surface(NV2AState *d, SurfaceBinding *surface, bool force) memory_region_set_client_dirty(d->vram, surface->vram_addr, surface->pitch * surface->height, DIRTY_MEMORY_NV2A_TEX); - memory_region_set_client_dirty(d->vram, surface->vram_addr, - surface->pitch * surface->height, - DIRTY_MEMORY_NV2A); surface->download_pending = false; surface->draw_dirty = false; - surface->download_generation = surface->draw_generation; } void pgraph_vk_wait_for_surface_download(SurfaceBinding *surface) @@ -974,87 +822,6 @@ static void set_surface_label(PGRAPHState *pg, SurfaceBinding const *surface) } } -#define SURFACE_IMAGE_POOL_MAX_SIZE 64 - -void pgraph_vk_surface_image_pool_init(PGRAPHVkState *r) -{ - QTAILQ_INIT(&r->surface_image_pool); - r->surface_image_pool_count = 0; -} - -static bool surface_image_pool_config_match(const SurfaceImageConfig *a, - const SurfaceImageConfig *b) -{ - return a->format == b->format && - a->width == b->width && - a->height == b->height && - a->usage == b->usage; -} - -static bool surface_image_pool_acquire(PGRAPHVkState *r, - const SurfaceImageConfig *config, - VkImage *out_image, - VmaAllocation *out_alloc, - VkImage *out_scratch, - VmaAllocation *out_scratch_alloc) -{ - PooledSurfaceImage *entry; - QTAILQ_FOREACH(entry, &r->surface_image_pool, entry) { - if (surface_image_pool_config_match(&entry->config, config)) { - *out_image = entry->image; - *out_alloc = entry->allocation; - *out_scratch = entry->image_scratch; - *out_scratch_alloc = entry->allocation_scratch; - QTAILQ_REMOVE(&r->surface_image_pool, entry, entry); - g_free(entry); - r->surface_image_pool_count--; - return true; - } - } - return false; -} - -static void surface_image_pool_release(PGRAPHVkState *r, - const SurfaceImageConfig *config, - VkImage image, - VmaAllocation allocation, - VkImage image_scratch, - VmaAllocation allocation_scratch) -{ - if (r->surface_image_pool_count >= SURFACE_IMAGE_POOL_MAX_SIZE) { - PooledSurfaceImage *oldest = QTAILQ_FIRST(&r->surface_image_pool); - assert(oldest != NULL); - QTAILQ_REMOVE(&r->surface_image_pool, oldest, entry); - vmaDestroyImage(r->allocator, oldest->image, oldest->allocation); - vmaDestroyImage(r->allocator, oldest->image_scratch, - oldest->allocation_scratch); - g_free(oldest); - r->surface_image_pool_count--; - } - - PooledSurfaceImage *entry = g_malloc(sizeof(PooledSurfaceImage)); - entry->config = *config; - entry->image = image; - entry->allocation = allocation; - entry->image_scratch = image_scratch; - entry->allocation_scratch = allocation_scratch; - QTAILQ_INSERT_TAIL(&r->surface_image_pool, entry, entry); - r->surface_image_pool_count++; -} - -void pgraph_vk_surface_image_pool_drain(PGRAPHVkState *r) -{ - PooledSurfaceImage *entry, *next; - QTAILQ_FOREACH_SAFE(entry, &r->surface_image_pool, entry, next) { - QTAILQ_REMOVE(&r->surface_image_pool, entry, entry); - vmaDestroyImage(r->allocator, entry->image, entry->allocation); - vmaDestroyImage(r->allocator, entry->image_scratch, - entry->allocation_scratch); - g_free(entry); - } - r->surface_image_pool_count = 0; -} - static void create_surface_image(PGRAPHState *pg, SurfaceBinding *surface) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -1070,66 +837,36 @@ static void create_surface_image(PGRAPHState *pg, SurfaceBinding *surface) "Creating new surface image width=%d height=%d @ %08" HWADDR_PRIx, width, height, surface->vram_addr); - VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - surface->host_fmt.usage; - - SurfaceImageConfig pool_cfg = { + VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .extent.width = width, + .extent.height = height, + .extent.depth = 1, + .mipLevels = 1, + .arrayLayers = 1, .format = surface->host_fmt.vk_format, - .width = width, - .height = height, - .usage = usage, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | surface->host_fmt.usage, + .samples = VK_SAMPLE_COUNT_1_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; - if (surface_image_pool_acquire(r, &pool_cfg, - &surface->image, &surface->allocation, - &surface->image_scratch, - &surface->allocation_scratch)) { - surface->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED; - } else { - VkImageCreateInfo image_create_info = { - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .imageType = VK_IMAGE_TYPE_2D, - .extent.width = width, - .extent.height = height, - .extent.depth = 1, - .mipLevels = 1, - .arrayLayers = 1, - .format = surface->host_fmt.vk_format, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .usage = usage, - .samples = VK_SAMPLE_COUNT_1_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - }; + VmaAllocationCreateInfo alloc_create_info = { + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + }; - VmaAllocationCreateInfo alloc_create_info = { - .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, - }; + VK_CHECK(vmaCreateImage(r->allocator, &image_create_info, + &alloc_create_info, &surface->image, + &surface->allocation, NULL)); - VkResult res = vmaCreateImage(r->allocator, &image_create_info, - &alloc_create_info, &surface->image, - &surface->allocation, NULL); - if (res != VK_SUCCESS) { - pgraph_vk_surface_image_pool_drain(r); - VK_CHECK(vmaCreateImage(r->allocator, &image_create_info, - &alloc_create_info, &surface->image, - &surface->allocation, NULL)); - } - - res = vmaCreateImage(r->allocator, &image_create_info, - &alloc_create_info, &surface->image_scratch, - &surface->allocation_scratch, NULL); - if (res != VK_SUCCESS) { - pgraph_vk_surface_image_pool_drain(r); - VK_CHECK(vmaCreateImage(r->allocator, &image_create_info, - &alloc_create_info, - &surface->image_scratch, - &surface->allocation_scratch, NULL)); - } - surface->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED; - } + VK_CHECK(vmaCreateImage(r->allocator, &image_create_info, + &alloc_create_info, &surface->image_scratch, + &surface->allocation_scratch, NULL)); + surface->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageViewCreateInfo image_view_create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, @@ -1152,9 +889,6 @@ static void create_surface_image(PGRAPHState *pg, SurfaceBinding *surface) VK_IMAGE_LAYOUT_UNDEFINED, surface->color ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - surface->image_layout = - surface->color ? VK_IMAGE_LAYOUT_GENERAL : - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; nv2a_profile_inc_counter(NV2A_PROF_QUEUE_SUBMIT_3); pgraph_vk_end_debug_marker(r, cmd); @@ -1166,7 +900,6 @@ static void migrate_surface_image(SurfaceBinding *dst, SurfaceBinding *src) { dst->image = src->image; dst->image_view = src->image_view; - dst->image_layout = src->image_layout; dst->allocation = src->allocation; dst->image_scratch = src->image_scratch; dst->image_scratch_current_layout = src->image_scratch_current_layout; @@ -1174,7 +907,6 @@ static void migrate_surface_image(SurfaceBinding *dst, SurfaceBinding *src) src->image = VK_NULL_HANDLE; src->image_view = VK_NULL_HANDLE; - src->image_layout = VK_IMAGE_LAYOUT_UNDEFINED; src->allocation = VK_NULL_HANDLE; src->image_scratch = VK_NULL_HANDLE; src->image_scratch_current_layout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -1186,28 +918,12 @@ static void destroy_surface_image(PGRAPHVkState *r, SurfaceBinding *surface) vkDestroyImageView(r->device, surface->image_view, NULL); surface->image_view = VK_NULL_HANDLE; - unsigned int width = surface->width ? surface->width : 1; - unsigned int height = surface->height ? surface->height : 1; - unsigned int scale_factor = g_nv2a->pgraph.surface_scale_factor; - if (scale_factor > 1) { - width *= scale_factor; - height *= scale_factor; - } - SurfaceImageConfig pool_cfg = { - .format = surface->host_fmt.vk_format, - .width = width, - .height = height, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | surface->host_fmt.usage, - }; - surface_image_pool_release(r, &pool_cfg, - surface->image, surface->allocation, - surface->image_scratch, - surface->allocation_scratch); - + vmaDestroyImage(r->allocator, surface->image, surface->allocation); surface->image = VK_NULL_HANDLE; surface->allocation = VK_NULL_HANDLE; + + vmaDestroyImage(r->allocator, surface->image_scratch, + surface->allocation_scratch); surface->image_scratch = VK_NULL_HANDLE; surface->allocation_scratch = VK_NULL_HANDLE; } @@ -1316,11 +1032,7 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface, nv2a_profile_inc_counter(NV2A_PROF_SURF_UPLOAD); - /* - * begin_nondraw_commands ends the active render pass, and the upload path - * transitions the image into TRANSFER_DST. That is sufficient - * synchronization here, so avoid the full finish stall. - */ + pgraph_vk_finish(pg, VK_FINISH_REASON_SURFACE_CREATE); // FIXME: SURFACE_UP trace_nv2a_pgraph_surface_upload( surface->color ? "COLOR" : "ZETA", @@ -1360,27 +1072,7 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface, // StorageBuffer *copy_buffer = &r->storage_buffers[BUFFER_STAGING_SRC]; - const uint8_t *upload_src = gl_read_buf; - unsigned int upload_src_stride = surface->pitch; - unsigned int upload_bytes_per_pixel = surface->fmt.bytes_per_pixel; - unsigned int upload_dst_stride = surface->width * upload_bytes_per_pixel; - -#ifdef __ANDROID__ - g_autofree uint8_t *converted_upload_buf = NULL; - if (android_vk_surface_uses_host_conversion(surface)) { - upload_bytes_per_pixel = surface->host_fmt.host_bytes_per_pixel; - upload_src_stride = surface->width * upload_bytes_per_pixel; - upload_dst_stride = upload_src_stride; - converted_upload_buf = - g_malloc((size_t)surface->height * upload_src_stride); - android_vk_surface_convert_to_host(surface, gl_read_buf, - surface->width, surface->height, - surface->pitch, - converted_upload_buf, - upload_src_stride); - upload_src = converted_upload_buf; - } -#endif + unsigned int upload_dst_stride = surface->width * surface->fmt.bytes_per_pixel; size_t uploaded_image_size = surface->height * upload_dst_stride; assert(uploaded_image_size <= copy_buffer->buffer_size); @@ -1398,8 +1090,8 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface, use_compute_to_convert_depth_stencil_format; assert(no_conversion_necessary); - memcpy_image(mapped_memory_ptr, upload_src, upload_dst_stride, - upload_src_stride, surface->height); + memcpy_image(mapped_memory_ptr, gl_read_buf, upload_dst_stride, + surface->pitch, surface->height); vmaFlushAllocation(r->allocator, copy_buffer->allocation, 0, VK_WHOLE_SIZE); vmaUnmapMemory(r->allocator, copy_buffer->allocation); @@ -1594,9 +1286,9 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface, pgraph_vk_transition_image_layout( pg, cmd, surface->image, surface->host_fmt.vk_format, - surface->image_layout, + surface->color ? VK_IMAGE_LAYOUT_GENERAL : + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; bool upscale = pg->surface_scale_factor > 1 && !use_compute_to_convert_depth_stencil_format; @@ -1649,9 +1341,6 @@ void pgraph_vk_upload_surface_data(NV2AState *d, SurfaceBinding *surface, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, surface->color ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - surface->image_layout = - surface->color ? VK_IMAGE_LAYOUT_GENERAL : - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; nv2a_profile_inc_counter(NV2A_PROF_QUEUE_SUBMIT_2); pgraph_vk_end_debug_marker(r, cmd); @@ -1749,14 +1438,11 @@ static void populate_surface_binding_target_sized(NV2AState *d, bool color, target->upload_pending = true; target->download_pending = false; target->draw_dirty = false; - target->draw_generation = 0; - target->download_generation = 0; target->dma_addr = dma.address; target->dma_len = dma.limit; target->frame_time = pg->frame_time; target->draw_time = pg->draw_time; target->cleared = false; - target->image_layout = VK_IMAGE_LAYOUT_UNDEFINED; target->initialized = false; } @@ -1855,7 +1541,6 @@ static void update_surface_part(NV2AState *d, bool upload, bool color) assert(!(target.swizzle && pg->clearing)); -#if 0 if (surface->swizzle != target.swizzle) { // Clears should only be done on linear surfaces. Avoid // synchronization by allowing (1) a surface marked swizzled to @@ -1870,7 +1555,6 @@ static void update_surface_part(NV2AState *d, bool upload, bool color) target.swizzle ? "swizzled" : "linear"); } } -#endif if (is_compatible && color && !check_surface_compatibility(surface, &target, true)) { @@ -1904,16 +1588,13 @@ static void update_surface_part(NV2AState *d, bool upload, bool color) "incompatible", surface->vram_addr); compare_surfaces(surface, &target); pgraph_vk_finish(pg, VK_FINISH_REASON_SURFACE_DOWN); - if (surface->draw_dirty) { - size_t region = (size_t)surface->pitch * surface->height; - memset(d->vram_ptr + surface->vram_addr, 0xFF, region); - memory_region_set_client_dirty( - d->vram, surface->vram_addr, region, - DIRTY_MEMORY_NV2A_TEX); - memory_region_set_client_dirty( - d->vram, surface->vram_addr, region, - DIRTY_MEMORY_VGA); - } + /* + * Download the GPU-rendered content to VRAM before + * shelving. This ensures VRAM has the correct pixel + * data for any subsequent texture or surface upload + * at this address. Matches the GL renderer behavior. + */ + pgraph_vk_surface_download_if_dirty(d, surface); shelve_surface(d, surface); } } @@ -2029,9 +1710,6 @@ void pgraph_vk_surface_update(NV2AState *d, bool upload, bool color_write, update_surface_part(d, true, false); } } else { -#if OPT_REORDER_SAFE_WINDOWS - pgraph_vk_flush_reorder_window(d); -#endif if ((color_write || pg->surface_color.write_enabled_cache) && pg->surface_color.draw_dirty) { update_surface_part(d, false, true); @@ -2150,9 +1828,9 @@ void pgraph_vk_init_surfaces(PGRAPHState *pg) } if (color_fallbacks[i].fallback1 && - check_format_and_usage_supported(r, - color_fallbacks[i].fallback1->vk_format, - color_fallbacks[i].fallback1->usage)) { + check_format_and_usage_supported( + r, color_fallbacks[i].fallback1->vk_format, + color_fallbacks[i].fallback1->usage)) { fallback = color_fallbacks[i].fallback1; } else if (color_fallbacks[i].fallback2 && check_format_and_usage_supported( @@ -2192,7 +1870,6 @@ void pgraph_vk_init_surfaces(PGRAPHState *pg) QTAILQ_INIT(&r->surfaces); QTAILQ_INIT(&r->invalid_surfaces); QTAILQ_INIT(&r->shelved_surfaces); - pgraph_vk_surface_image_pool_init(r); r->downloads_pending = false; qemu_event_init(&r->downloads_complete, false); @@ -2236,7 +1913,6 @@ void pgraph_vk_surface_flush(NV2AState *d) g_free(s); } prune_invalid_surfaces(r, 0); - pgraph_vk_surface_image_pool_drain(r); pgraph_vk_reload_surface_scale_factor(pg); } diff --git a/hw/xbox/nv2a/pgraph/vk/texture.c b/hw/xbox/nv2a/pgraph/vk/texture.c index aadf3571b8..680d246259 100644 --- a/hw/xbox/nv2a/pgraph/vk/texture.c +++ b/hw/xbox/nv2a/pgraph/vk/texture.c @@ -98,8 +98,10 @@ static uint8_t *android_vk_texture_convert_to_rgba8(TextureShape s, out[1] = android_vk_expand_5_to_8((pixel >> 5) & 0x1F); out[2] = android_vk_expand_5_to_8(pixel & 0x1F); out[3] = - (s.color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5 || - s.color_format == NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5) ? + (s.color_format == + NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5 || + s.color_format == + NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5) ? 0xFF : ((pixel & 0x8000) ? 0xFF : 0x00); } @@ -689,13 +691,6 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac TextureShape *state = &texture->key.state; VkColorFormatInfo vkf = kelvin_color_format_vk_map[state->color_format]; -#if OPT_REORDER_SAFE_WINDOWS - if (r->reorder_window.count > 0) { - NV2AState *d = container_of(pg, NV2AState, pgraph); - pgraph_vk_flush_reorder_window(d); - } -#endif - bool use_compute_to_convert_depth_stencil = surface->host_fmt.vk_format == VK_FORMAT_D24_UNORM_S8_UINT || surface->host_fmt.vk_format == VK_FORMAT_D32_SFLOAT_S8_UINT; @@ -718,211 +713,65 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac scaled_height = surface->height; pgraph_apply_scaling_factor(pg, &scaled_width, &scaled_height); - StorageBuffer *dst_storage_buffer = &r->storage_buffers[BUFFER_COMPUTE_DST]; - VkBuffer texture_source_buffer; + size_t copied_image_size = + scaled_width * scaled_height * surface->host_fmt.host_bytes_per_pixel; + size_t stencil_buffer_offset = 0; + size_t stencil_buffer_size = 0; - if (use_compute_to_convert_depth_stencil && - (surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT)) { - size_t packed_image_size = scaled_width * scaled_height * 4; - size_t stencil_buffer_offset = + int num_regions = 0; + VkBufferImageCopy regions[2]; + regions[num_regions++] = (VkBufferImageCopy){ + .bufferOffset = 0, + .bufferRowLength = 0, // Tightly packed + .bufferImageHeight = 0, // Tightly packed + .imageSubresource.aspectMask = surface->color ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, + .imageSubresource.mipLevel = 0, + .imageSubresource.baseArrayLayer = 0, + .imageSubresource.layerCount = 1, + .imageOffset = (VkOffset3D){0, 0, 0}, + .imageExtent = (VkExtent3D){scaled_width, scaled_height, 1}, + }; + + if (surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT) { + stencil_buffer_offset = ROUND_UP(scaled_width * scaled_height * 4, r->device_props.limits.minStorageBufferOffsetAlignment); - size_t stencil_buffer_size = scaled_width * scaled_height; - assert(dst_storage_buffer->buffer_size >= - stencil_buffer_offset + stencil_buffer_size); + stencil_buffer_size = scaled_width * scaled_height; + copied_image_size += stencil_buffer_size; - VkBufferImageCopy stencil_region = { + regions[num_regions++] = (VkBufferImageCopy){ .bufferOffset = stencil_buffer_offset, - .bufferRowLength = 0, - .bufferImageHeight = 0, + .bufferRowLength = 0, // Tightly packed + .bufferImageHeight = 0, // Tightly packed .imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT, .imageSubresource.mipLevel = 0, .imageSubresource.baseArrayLayer = 0, .imageSubresource.layerCount = 1, - .imageOffset = (VkOffset3D){ 0, 0, 0 }, - .imageExtent = (VkExtent3D){ scaled_width, scaled_height, 1 }, + .imageOffset = (VkOffset3D){0, 0, 0}, + .imageExtent = (VkExtent3D){scaled_width, scaled_height, 1}, }; + } + StorageBuffer *dst_storage_buffer = &r->storage_buffers[BUFFER_COMPUTE_DST]; + assert(dst_storage_buffer->buffer_size >= copied_image_size); - pgraph_vk_transition_image_layout( - pg, cmd, surface->image, surface->host_fmt.vk_format, - surface->image_layout, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + pgraph_vk_transition_image_layout( + pg, cmd, surface->image, surface->host_fmt.vk_format, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - vkCmdCopyImageToBuffer(cmd, surface->image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dst_storage_buffer->buffer, 1, &stencil_region); + vkCmdCopyImageToBuffer( + cmd, surface->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dst_storage_buffer->buffer, + num_regions, regions); - VkImageMemoryBarrier depth_read_barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = surface->image, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - }; - vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, - 0, NULL, 1, &depth_read_barrier); - surface->image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + pgraph_vk_transition_image_layout( + pg, cmd, surface->image, surface->host_fmt.vk_format, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - VkImageViewCreateInfo depth_view_info = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = surface->image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = surface->host_fmt.vk_format, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; - VkImageView depth_view; - VK_CHECK(vkCreateImageView(r->device, &depth_view_info, NULL, - &depth_view)); - - VkBufferMemoryBarrier stencil_barrier = { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = dst_storage_buffer->buffer, - .offset = stencil_buffer_offset, - .size = stencil_buffer_size, - }; - VkBufferMemoryBarrier output_pre_barrier = { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = r->storage_buffers[BUFFER_COMPUTE_SRC].buffer, - .size = packed_image_size, - }; - VkBufferMemoryBarrier pre_barriers[] = { - stencil_barrier, - output_pre_barrier, - }; - vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, - ARRAY_SIZE(pre_barriers), pre_barriers, 0, NULL); - - pgraph_vk_pack_depth_stencil_direct( - pg, surface, cmd, depth_view, dst_storage_buffer->buffer, - stencil_buffer_offset, stencil_buffer_size, - r->storage_buffers[BUFFER_COMPUTE_SRC].buffer, false); - - VkBufferMemoryBarrier post_compute_barrier = { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = r->storage_buffers[BUFFER_COMPUTE_SRC].buffer, - .size = packed_image_size, - }; - vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 1, - &post_compute_barrier, 0, NULL); - - VkImageMemoryBarrier depth_restore_barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = surface->image, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | - VK_IMAGE_ASPECT_STENCIL_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, - .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - }; - vkCmdPipelineBarrier( - cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - 0, 0, NULL, 0, NULL, 1, &depth_restore_barrier); - surface->image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - vkDestroyImageView(r->device, depth_view, NULL); - texture_source_buffer = r->storage_buffers[BUFFER_COMPUTE_SRC].buffer; - } else if (use_compute_to_convert_depth_stencil) { - size_t copied_image_size = - scaled_width * scaled_height * surface->host_fmt.host_bytes_per_pixel; - size_t stencil_buffer_offset = 0; - size_t stencil_buffer_size = 0; - - int num_regions = 0; - VkBufferImageCopy regions[2]; - regions[num_regions++] = (VkBufferImageCopy){ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, - .imageSubresource.mipLevel = 0, - .imageSubresource.baseArrayLayer = 0, - .imageSubresource.layerCount = 1, - .imageOffset = (VkOffset3D){ 0, 0, 0 }, - .imageExtent = (VkExtent3D){ scaled_width, scaled_height, 1 }, - }; - - if (surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT) { - stencil_buffer_offset = - ROUND_UP(scaled_width * scaled_height * 4, - r->device_props.limits.minStorageBufferOffsetAlignment); - stencil_buffer_size = scaled_width * scaled_height; - copied_image_size += stencil_buffer_size; - - regions[num_regions++] = (VkBufferImageCopy){ - .bufferOffset = stencil_buffer_offset, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT, - .imageSubresource.mipLevel = 0, - .imageSubresource.baseArrayLayer = 0, - .imageSubresource.layerCount = 1, - .imageOffset = (VkOffset3D){ 0, 0, 0 }, - .imageExtent = (VkExtent3D){ scaled_width, scaled_height, 1 }, - }; - } - assert(dst_storage_buffer->buffer_size >= copied_image_size); - - pgraph_vk_transition_image_layout( - pg, cmd, surface->image, surface->host_fmt.vk_format, - surface->image_layout, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - - vkCmdCopyImageToBuffer(cmd, surface->image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dst_storage_buffer->buffer, num_regions, - regions); - - pgraph_vk_transition_image_layout( - pg, cmd, surface->image, surface->host_fmt.vk_format, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - surface->image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkBuffer texture_source_buffer; + if (use_compute_to_convert_depth_stencil) { size_t packed_image_size = scaled_width * scaled_height * 4; VkBufferMemoryBarrier pre_pack_src_barrier = { @@ -984,38 +833,6 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac texture_source_buffer = r->storage_buffers[BUFFER_COMPUTE_SRC].buffer; } else { - size_t copied_image_size = - scaled_width * scaled_height * surface->host_fmt.host_bytes_per_pixel; - int num_regions = 0; - VkBufferImageCopy regions[2]; - regions[num_regions++] = (VkBufferImageCopy){ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, - .imageSubresource.mipLevel = 0, - .imageSubresource.baseArrayLayer = 0, - .imageSubresource.layerCount = 1, - .imageOffset = (VkOffset3D){ 0, 0, 0 }, - .imageExtent = (VkExtent3D){ scaled_width, scaled_height, 1 }, - }; - assert(dst_storage_buffer->buffer_size >= copied_image_size); - - pgraph_vk_transition_image_layout( - pg, cmd, surface->image, surface->host_fmt.vk_format, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - - vkCmdCopyImageToBuffer(cmd, surface->image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dst_storage_buffer->buffer, num_regions, - regions); - - pgraph_vk_transition_image_layout( - pg, cmd, surface->image, surface->host_fmt.vk_format, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - VkBufferMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, @@ -1037,7 +854,7 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); texture->current_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - VkBufferImageCopy output_region = { + regions[0] = (VkBufferImageCopy){ .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, @@ -1050,7 +867,7 @@ static void copy_zeta_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surfac }; vkCmdCopyBufferToImage( cmd, texture_source_buffer, texture->image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &output_region); + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, regions); VkBufferMemoryBarrier post_copy_barrier = { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, @@ -1110,107 +927,6 @@ static void bind_surface_as_texture(PGRAPHState *pg, SurfaceBinding *surface, texture->draw_time = surface->draw_time; } -static bool color_surface_can_direct_bind(const SurfaceBinding *surface, - const TextureBinding *texture) -{ - VkColorFormatInfo tex_vkf = - kelvin_color_format_vk_map[texture->key.state.color_format]; - - return surface->color && tex_vkf.vk_format && - surface->host_fmt.vk_format == tex_vkf.vk_format; -} - -static bool surface_can_direct_bind(const SurfaceBinding *surface, - const TextureBinding *texture) -{ - if (surface->color) { - return color_surface_can_direct_bind(surface, texture); - } - - return !(surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT); -} - -static VkImageView get_surface_direct_texture_view(PGRAPHVkState *r, - SurfaceBinding *surface, - TextureBinding *texture) -{ - assert(surface->color); - assert(color_surface_can_direct_bind(surface, texture)); - - if (texture->direct_surface_view != VK_NULL_HANDLE && - texture->direct_surface_image == surface->image) { - return texture->direct_surface_view; - } - - if (texture->direct_surface_view != VK_NULL_HANDLE) { - vkDestroyImageView(r->device, texture->direct_surface_view, NULL); - texture->direct_surface_view = VK_NULL_HANDLE; - texture->direct_surface_image = VK_NULL_HANDLE; - } - - VkColorFormatInfo tex_vkf = - kelvin_color_format_vk_map[texture->key.state.color_format]; - VkImageViewCreateInfo view_info = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = surface->image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = surface->host_fmt.vk_format, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .components = tex_vkf.component_map, - }; - - VK_CHECK(vkCreateImageView(r->device, &view_info, NULL, - &texture->direct_surface_view)); - texture->direct_surface_image = surface->image; - return texture->direct_surface_view; -} - -static void bind_zeta_surface_as_texture(PGRAPHState *pg, SurfaceBinding *surface, - TextureBinding *texture) -{ - assert(!surface->color); - assert(!(surface->host_fmt.aspect & VK_IMAGE_ASPECT_STENCIL_BIT)); - - nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX); - - if (surface->image_layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { - VkCommandBuffer cmd = pgraph_vk_begin_nondraw_commands(pg); - - VkImageMemoryBarrier barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .oldLayout = surface->image_layout, - .newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = surface->image, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - }; - vkCmdPipelineBarrier( - cmd, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 0, NULL, 0, NULL, 1, &barrier); - - pgraph_vk_end_nondraw_commands(pg, cmd); - surface->image_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; - } - - texture->draw_time = surface->draw_time; -} - static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface, TextureBinding *texture) { @@ -1223,13 +939,6 @@ static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface, TextureShape *state = &texture->key.state; VkColorFormatInfo vkf = kelvin_color_format_vk_map[state->color_format]; -#if OPT_REORDER_SAFE_WINDOWS - if (r->reorder_window.count > 0) { - NV2AState *d = container_of(pg, NV2AState, pgraph); - pgraph_vk_flush_reorder_window(d); - } -#endif - nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX); trace_nv2a_pgraph_surface_render_to_texture( @@ -1240,9 +949,9 @@ static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface, pgraph_vk_transition_image_layout( pg, cmd, surface->image, surface->host_fmt.vk_format, - surface->image_layout, + surface->color ? VK_IMAGE_LAYOUT_GENERAL : + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - surface->image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; pgraph_vk_transition_image_layout(pg, cmd, texture->image, vkf.vk_format, texture->current_layout, @@ -1269,9 +978,6 @@ static void copy_surface_to_texture(PGRAPHState *pg, SurfaceBinding *surface, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, surface->color ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - surface->image_layout = - surface->color ? VK_IMAGE_LAYOUT_GENERAL : - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; pgraph_vk_transition_image_layout(pg, cmd, texture->image, vkf.vk_format, texture->current_layout, @@ -1322,17 +1028,6 @@ static bool check_surface_to_texture_compatiblity(const SurfaceBinding *surface, surface->host_fmt.host_bytes_per_pixel == vk_format_texel_size(tex_vkf.vk_format); } -#if OPT_BINDLESS_TEXTURES -static void update_bindless_texture_descriptor(PGRAPHVkState *r, - uint32_t binding, - uint32_t slot, - VkImageView image_view, - VkImageLayout image_layout, - VkSampler sampler); -static void set_texture_bindless_index(PGRAPHState *pg, int texture_idx, - TextureBinding *binding); -#endif - static void create_dummy_texture(PGRAPHState *pg) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -1458,17 +1153,7 @@ static void create_dummy_texture(PGRAPHState *pg) .allocation = texture_allocation, .image_view = texture_image_view, .sampler = texture_sampler, -#if OPT_BINDLESS_TEXTURES - .bindless_slot = 0, - .bindless_binding = 0, -#endif }; - -#if OPT_BINDLESS_TEXTURES - update_bindless_texture_descriptor( - r, 0, 0, texture_image_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - texture_sampler); -#endif } static void destroy_dummy_texture(PGRAPHVkState *r) @@ -1476,60 +1161,6 @@ static void destroy_dummy_texture(PGRAPHVkState *r) texture_cache_release_node_resources(r, &r->dummy_texture); } -#if OPT_BINDLESS_TEXTURES -static void update_bindless_texture_descriptor(PGRAPHVkState *r, - uint32_t binding, - uint32_t slot, - VkImageView image_view, - VkImageLayout image_layout, - VkSampler sampler) -{ - if (!r->bindless_textures_supported) { - return; - } - - VkDescriptorImageInfo image_info = { - .imageLayout = image_layout, - .imageView = image_view, - .sampler = sampler, - }; - VkWriteDescriptorSet write = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = r->bindless_descriptor_set, - .dstBinding = binding, - .dstArrayElement = slot, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, - .pImageInfo = &image_info, - }; - vkUpdateDescriptorSets(r->device, 1, &write, 0, NULL); -} - -static void set_texture_bindless_index(PGRAPHState *pg, int texture_idx, - TextureBinding *binding) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - uint32_t slot = 0; - - if (!r->bindless_textures_supported) { - return; - } - - if (binding != NULL) { - slot = binding->bindless_slot; - if (r->tex_surface_direct[texture_idx]) { - slot = BINDLESS_STAGE_SLOT_BASE + texture_idx; - update_bindless_texture_descriptor( - r, binding->bindless_binding, slot, - r->tex_surface_direct_views[texture_idx], - r->tex_surface_direct_layout[texture_idx], binding->sampler); - } - } - - r->tex_bindless_indices[texture_idx] = slot; -} -#endif - static void set_texture_label(PGRAPHState *pg, TextureBinding *texture) { PGRAPHVkState *r = pg->vk_renderer_state; @@ -1611,8 +1242,6 @@ static void create_texture(PGRAPHState *pg, int texture_idx) bool surface_to_texture = false; r->tex_surface_direct[texture_idx] = false; r->tex_surface_direct_views[texture_idx] = VK_NULL_HANDLE; - r->tex_surface_direct_layout[texture_idx] = - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // Check active surfaces to see if this texture was a render target SurfaceBinding *surface = pgraph_vk_surface_get(d, texture_vram_offset); @@ -1626,6 +1255,17 @@ static void create_texture(PGRAPHState *pg, int texture_idx) state.color_format); } + /* + * When a surface has a pending VRAM upload (upload_pending=true), + * VRAM may contain CPU-written content that is newer than the + * VkImage. Skip s2t and let the texture read from VRAM instead. + * This handles cases where the CPU writes UI/text content to VRAM + * at a framebuffer address while the VkImage still has old GPU content. + */ + if (surface_to_texture && surface->upload_pending) { + surface_to_texture = false; + } + if (surface_to_texture && surface->upload_pending) { pgraph_vk_upload_surface_data(d, surface, false); } @@ -1646,6 +1286,17 @@ static void create_texture(PGRAPHState *pg, int texture_idx) } } + /* + * If a surface exists at the texture address but is not compatible for + * direct surface-to-texture binding (e.g. dimension mismatch or + * upload_pending), ensure the surface's GPU-rendered content is + * downloaded to VRAM so the texture upload reads fresh data. + */ + if (!surface_to_texture && surface && surface->draw_dirty) { + pgraph_vk_surface_download_if_dirty(d, surface); + possibly_dirty = true; + } + if (!surface_to_texture) { // FIXME: Restructure to support rendering surfaces to cubemap faces @@ -1672,29 +1323,9 @@ static void create_texture(PGRAPHState *pg, int texture_idx) } if (!surface_to_texture && !possibly_dirty_checked) { - bool skip_dirty_check = binding_found && - snode->dirty_check_frame == pg->frame_time && - !snode->dirty_check_result; - if (!skip_dirty_check) { - bool vram_dirty = check_texture_possibly_dirty( - d, texture_vram_offset, texture_length, - texture_palette_vram_offset, texture_palette_data_size); - possibly_dirty |= vram_dirty; - if (binding_found) { - snode->dirty_check_frame = pg->frame_time; - snode->dirty_check_result = vram_dirty; - } - } - } - - if (binding_found && possibly_dirty && !surface_to_texture) { - bool vram_confirmed_clean = - snode->dirty_check_frame == pg->frame_time && - !snode->dirty_check_result; - if (vram_confirmed_clean) { - snode->possibly_dirty = false; - possibly_dirty = false; - } + possibly_dirty |= check_texture_possibly_dirty( + d, texture_vram_offset, texture_length, texture_palette_vram_offset, + texture_palette_data_size); } // Calculate hash of texture data, if necessary @@ -1711,29 +1342,12 @@ static void create_texture(PGRAPHState *pg, int texture_idx) if (binding_found) { if (surface_to_texture) { - bool can_direct_bind = surface_can_direct_bind(surface, snode); - if (can_direct_bind) { - VkImageLayout direct_layout; - if (surface->color) { - if (surface->draw_time != snode->draw_time) { - bind_surface_as_texture(pg, surface, snode); - } - direct_layout = VK_IMAGE_LAYOUT_GENERAL; - r->tex_surface_direct_views[texture_idx] = - get_surface_direct_texture_view(r, surface, snode); - } else { - if (surface->draw_time != snode->draw_time || - surface->image_layout != - VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) { - bind_zeta_surface_as_texture(pg, surface, snode); - } - direct_layout = - VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; - r->tex_surface_direct_views[texture_idx] = - surface->image_view; + if (surface->color) { + if (surface->draw_time != snode->draw_time) { + bind_surface_as_texture(pg, surface, snode); } r->tex_surface_direct[texture_idx] = true; - r->tex_surface_direct_layout[texture_idx] = direct_layout; + r->tex_surface_direct_views[texture_idx] = surface->image_view; } else if (surface->draw_time != snode->draw_time) { copy_surface_to_texture(pg, surface, snode); } @@ -1744,9 +1358,6 @@ static void create_texture(PGRAPHState *pg, int texture_idx) } } -#if OPT_BINDLESS_TEXTURES - set_texture_bindless_index(pg, texture_idx, snode); -#endif NV2A_VK_DGROUP_END(); return; } @@ -1944,40 +1555,13 @@ static void create_texture(PGRAPHState *pg, int texture_idx) set_texture_label(pg, snode); -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported) { - if (state.cubemap) { - snode->bindless_binding = 2; - } else if (state.dimensionality == 3) { - snode->bindless_binding = 1; - } else { - snode->bindless_binding = 0; - } - update_bindless_texture_descriptor( - r, snode->bindless_binding, snode->bindless_slot, - snode->image_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - snode->sampler); - } -#endif - r->texture_bindings[texture_idx] = snode; if (surface_to_texture) { - bool can_direct_bind = surface_can_direct_bind(surface, snode); - if (can_direct_bind) { - VkImageLayout direct_layout; - if (surface->color) { - bind_surface_as_texture(pg, surface, snode); - direct_layout = VK_IMAGE_LAYOUT_GENERAL; - r->tex_surface_direct_views[texture_idx] = - get_surface_direct_texture_view(r, surface, snode); - } else { - bind_zeta_surface_as_texture(pg, surface, snode); - direct_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; - r->tex_surface_direct_views[texture_idx] = surface->image_view; - } + if (surface->color) { + bind_surface_as_texture(pg, surface, snode); r->tex_surface_direct[texture_idx] = true; - r->tex_surface_direct_layout[texture_idx] = direct_layout; + r->tex_surface_direct_views[texture_idx] = surface->image_view; } else { copy_surface_to_texture(pg, surface, snode); } @@ -1986,9 +1570,6 @@ static void create_texture(PGRAPHState *pg, int texture_idx) snode->draw_time = 0; } -#if OPT_BINDLESS_TEXTURES - set_texture_bindless_index(pg, texture_idx, snode); -#endif NV2A_VK_DGROUP_END(); } @@ -2009,24 +1590,6 @@ static bool check_textures_dirty(PGRAPHState *pg) return false; } -bool pgraph_vk_check_textures_fast_skip(PGRAPHState *pg) -{ - PGRAPHVkState *r = pg->vk_renderer_state; - - for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { - if (!r->texture_bindings[i] || pg->texture_dirty[i] || - r->tex_surface_direct[i]) { - return false; - } - if (r->texture_bindings[i] != &r->dummy_texture && - r->texture_bindings[i]->possibly_dirty) { - return false; - } - } - - return true; -} - static void update_timestamps(PGRAPHVkState *r) { for (int i = 0; i < ARRAY_SIZE(r->texture_bindings); i++) { @@ -2055,24 +1618,6 @@ void pgraph_vk_bind_textures(NV2AState *d) return; } - for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { - TextureBinding *binding = r->texture_bindings[i]; - - if (!binding || binding == &r->dummy_texture || !binding->possibly_dirty - || binding->dirty_check_frame == pg->frame_time) { - continue; - } - - bool vram_dirty = check_texture_possibly_dirty( - d, binding->key.texture_vram_offset, binding->key.texture_length, - binding->key.palette_vram_offset, binding->key.palette_length); - binding->dirty_check_frame = pg->frame_time; - binding->dirty_check_result = vram_dirty; - if (!vram_dirty) { - binding->possibly_dirty = false; - } - } - for (int i = 0; i < NV2A_MAX_TEXTURES; i++) { if (!pgraph_is_texture_enabled(pg, i)) { if (r->texture_bindings[i] != &r->dummy_texture) { @@ -2081,12 +1626,7 @@ void pgraph_vk_bind_textures(NV2AState *d) } r->tex_surface_direct[i] = false; r->tex_surface_direct_views[i] = VK_NULL_HANDLE; - r->tex_surface_direct_layout[i] = - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; pg->texture_dirty[i] = false; -#if OPT_BINDLESS_TEXTURES - r->tex_bindless_indices[i] = 0; -#endif continue; } @@ -2100,14 +1640,12 @@ void pgraph_vk_bind_textures(NV2AState *d) TextureBinding *prev_binding = r->texture_bindings[i]; bool prev_direct = r->tex_surface_direct[i]; VkImageView prev_direct_view = r->tex_surface_direct_views[i]; - VkImageLayout prev_direct_layout = r->tex_surface_direct_layout[i]; create_texture(pg, i); pg->texture_dirty[i] = false; // FIXME: Move to renderer? if (r->texture_bindings[i] != prev_binding || r->tex_surface_direct[i] != prev_direct || - r->tex_surface_direct_views[i] != prev_direct_view || - r->tex_surface_direct_layout[i] != prev_direct_layout) { + r->tex_surface_direct_views[i] != prev_direct_view) { r->texture_bindings_changed = true; } } @@ -2118,7 +1656,6 @@ void pgraph_vk_bind_textures(NV2AState *d) static void texture_cache_entry_init(Lru *lru, LruNode *node, const void *state) { - PGRAPHVkState *r = container_of(lru, PGRAPHVkState, texture_cache); TextureBinding *snode = container_of(node, TextureBinding, node); snode->image = VK_NULL_HANDLE; @@ -2127,30 +1664,6 @@ static void texture_cache_entry_init(Lru *lru, LruNode *node, const void *state) snode->direct_surface_view = VK_NULL_HANDLE; snode->direct_surface_image = VK_NULL_HANDLE; snode->sampler = VK_NULL_HANDLE; - snode->submit_time = 0; - snode->dirty_check_frame = 0; - snode->dirty_check_result = false; -#if OPT_BINDLESS_TEXTURES - snode->bindless_slot = 0; - snode->bindless_binding = 0; - if (r->bindless_textures_supported) { - for (uint32_t word = 0; - word < ARRAY_SIZE(r->bindless_slot_bitmap); word++) { - if (r->bindless_slot_bitmap[word] == UINT64_MAX) { - continue; - } - uint32_t bit = __builtin_ctzll(~r->bindless_slot_bitmap[word]); - uint32_t slot = word * 64 + bit; - if (slot >= BINDLESS_STAGE_SLOT_BASE) { - break; - } - snode->bindless_slot = slot; - r->bindless_slot_bitmap[word] |= (1ULL << bit); - break; - } - assert(snode->bindless_slot > 0); - } -#endif } static void texture_cache_release_node_resources(PGRAPHVkState *r, TextureBinding *snode) @@ -2197,13 +1710,6 @@ static void texture_cache_entry_post_evict(Lru *lru, LruNode *node) { PGRAPHVkState *r = container_of(lru, PGRAPHVkState, texture_cache); TextureBinding *snode = container_of(node, TextureBinding, node); -#if OPT_BINDLESS_TEXTURES - if (r->bindless_textures_supported && snode->bindless_slot > 0 && - snode->bindless_slot < BINDLESS_STAGE_SLOT_BASE) { - r->bindless_slot_bitmap[snode->bindless_slot / 64] &= - ~(1ULL << (snode->bindless_slot % 64)); - } -#endif texture_cache_release_node_resources(r, snode); } @@ -2217,7 +1723,7 @@ static bool texture_cache_entry_compare(Lru *lru, LruNode *node, static void texture_cache_init(PGRAPHVkState *r) { const size_t texture_cache_size = 1024; - lru_init(&r->texture_cache, 2048); + lru_init(&r->texture_cache, 1u << 16); r->texture_cache_entries = g_malloc_n(texture_cache_size, sizeof(TextureBinding)); assert(r->texture_cache_entries != NULL); for (int i = 0; i < texture_cache_size; i++) { @@ -2232,7 +1738,6 @@ static void texture_cache_init(PGRAPHVkState *r) static void texture_cache_finalize(PGRAPHVkState *r) { lru_flush(&r->texture_cache); - lru_destroy(&r->texture_cache); g_free(r->texture_cache_entries); r->texture_cache_entries = NULL; } @@ -2279,8 +1784,6 @@ void pgraph_vk_finalize_textures(PGRAPHState *pg) r->texture_bindings[i] = NULL; r->tex_surface_direct[i] = false; r->tex_surface_direct_views[i] = VK_NULL_HANDLE; - r->tex_surface_direct_layout[i] = - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } destroy_dummy_texture(r); diff --git a/implementation_plan.md b/implementation_plan.md new file mode 100644 index 0000000000..b28ce03ca2 --- /dev/null +++ b/implementation_plan.md @@ -0,0 +1,43 @@ +# HakuX Vulkan Port implementation + +The current Vulkan renderer on the `xemu` Android port experiences texture flickering, black textures, and missing rendering elements (like loading icons, text, etc). + +Based on a comparative analysis against the `hakuX` fork, the root cause traces back to how the Android Vulkan texture formats and surfaces are handled. `hakuX` implements a robust rendering pipeline utilizing generation counters, bindless descriptor indexing, native 16-bit packing formats (e.g. `VK_FORMAT_A1R5G5B5_UNORM_PACK16`), and surface general layout sampling. Conversely, `xemu` relies on experimental CPU-side pixel conversions to fall back to generic `RGBA8` formats on Android. These workarounds in `xemu` are explicitly causing the black textures. + +Additionally, `hakuX` features around ~20,000 lines of differences within the `hw/xbox/nv2a/pgraph/vk` rendering core specifically optimized for tiled Android GPUs (Adreno, Mali) and async pipeline processing. + +## Proposed Changes + +To accurately "copy what they did" and guarantee the same stable results for Android Vulkan, we will sync the heavily-optimized `hakuX` graphics backend directly over `xemu`'s implementation. + +### PGRAPH and NV2A Graphics Pipeline Core + +We will synchronize the modified Vulkan module and its heavily coupled PGRAPH CPU dependencies. + +#### [MODIFY] `hw/xbox/nv2a/pgraph/vk/` (Entire Directory) +- Replace all files with the upstream `hakuX` implementation. +- This includes dropping the problematic Android CPU texture format conversions in `constants.h` and adopting `hakuX`'s surface/texture generational caches, async shader compilers, and deferred frame pacing loops. +- New files added by `hakuX`: `render_thread.c` and `submit_worker.c`. + +#### [MODIFY] `hw/xbox/nv2a/pgraph/texture.c` +- Remove the unreliable `A1R5G5B5` and `A4R4G4B4` CPU texture upscaling methods specific to the current `xemu` build. + +#### [MODIFY] `hw/xbox/nv2a/pgraph/pgraph.c` & `hw/xbox/nv2a/pgraph/pgraph.h` +- Synchronize state declarations and Vulkan caching hooks introduced by `hakuX`. + +#### [MODIFY] `hw/xbox/nv2a/nv2a.c` & `hw/xbox/nv2a/nv2a_int.h` +- Ensure integration matches `hakuX`'s execution flow, particularly concerning asynchronous compilation polling. + +## User Review Required + +> [!WARNING] +> Replacing the Vulkan backend directory is a major architectural overwrite. It will overwrite the `RGBA8` pixel fallback commits recently merged into the Android port of `xemu` in favor of `hakuX`'s native implementation. Please confirm that you are okay with forcefully overriding `hw/xbox/nv2a/pgraph/vk/` with the `hakuX` variant. + +## Verification Plan + +### Automated Tests +- The build process will be verified statically to ensure successful C linkage and no regression in dependencies. + +### Manual Verification +- Compile the updated app to Android (`vulkan` driver selected). +- Validate that the black flickering textures are resolved and all 2D UI elements, including loading icons and words, render correctly during game boot. diff --git a/include/exec/translation-block.h b/include/exec/translation-block.h index 433fa3bc28..074466fb71 100644 --- a/include/exec/translation-block.h +++ b/include/exec/translation-block.h @@ -41,6 +41,19 @@ struct tb_tc { size_t size; }; +#ifdef XBOX +/* + * Metadata for a merged superblock TB. Tracks component B's guest + * code range so that page invalidation covers both code regions. + */ +typedef struct SuperblockInfo { + vaddr pc_b; /* Guest PC of component B */ + uint16_t size_b; /* Byte size of B's guest code */ + uint16_t icount_b; /* Instruction count of B */ + tb_page_addr_t phys_pc_b; /* Physical address of B (for invalidation) */ +} SuperblockInfo; +#endif + struct TranslationBlock { /* * Guest PC corresponding to this block. This must be the true @@ -81,9 +94,15 @@ struct TranslationBlock { #define CF_NOIRQ 0x00010000 /* Generate an uninterruptible TB */ #define CF_PCREL 0x00020000 /* Opcodes in TB are PC-relative */ #define CF_BP_PAGE 0x00040000 /* Breakpoint present in code page */ +#define CF_TIER1 0x00080000 /* Transient: Tier 1 optimized compilation */ +#define CF_SUPERBLOCK 0x00100000 /* Distinguishes superblock TBs in hash */ #define CF_CLUSTER_MASK 0xff000000 /* Top 8 bits are cluster ID */ #define CF_CLUSTER_SHIFT 24 +#ifdef XBOX +#define TB_TIER1_THRESHOLD 64 /* Promote to Tier 1 after this many executions */ +#endif + /* * Above fields used for comparing */ @@ -145,6 +164,22 @@ struct TranslationBlock { uintptr_t jmp_list_head; uintptr_t jmp_list_next[2]; uintptr_t jmp_dest[2]; + +#ifdef XBOX + uint32_t exec_count; /* Approximate execution count (saturating) */ + uint8_t tier; /* 0 = quick, 1 = optimized, 2 = superblock */ + /* + * Bitmask of CC globals (cc_op=0, cc_dst=1, cc_src=2, cc_src2=3) + * that are DEFINED (written) before any USE (read) in this TB. + * Used by tier1 cross-TB dead flag elimination: if a successor TB + * defines a CC global before reading it, the predecessor's store + * of that global at exit is dead. + */ + uint8_t cc_defines_first; + uint8_t tier_pad[1]; /* Alignment padding */ + uint32_t chain_count[2]; /* How many times each exit was taken */ + SuperblockInfo *superblock; /* Non-NULL if this is a merged superblock */ +#endif }; /* The alignment given to TranslationBlock during allocation. */ @@ -159,6 +194,12 @@ static inline uint32_t tb_cflags(const TranslationBlock *tb) bool tcg_cflags_has(CPUState *cpu, uint32_t flags); void tcg_cflags_set(CPUState *cpu, uint32_t flags); +#ifdef XBOX +bool tier1_has_pending_request(vaddr pc, uint64_t cs_base, uint32_t flags); +int tier1_consume_request(vaddr pc, uint64_t cs_base, uint32_t flags, + uint32_t *cflags_out); +#endif + static inline tb_page_addr_t tb_page_addr0(const TranslationBlock *tb) { #ifdef CONFIG_USER_ONLY diff --git a/include/exec/translator.h b/include/exec/translator.h index 3c32655569..419a4237c3 100644 --- a/include/exec/translator.h +++ b/include/exec/translator.h @@ -149,6 +149,7 @@ void translator_loop(CPUState *cpu, TranslationBlock *tb, int *max_insns, vaddr pc, void *host_pc, const TranslatorOps *ops, DisasContextBase *db); + /** * translator_use_goto_tb * @db: Disassembly context diff --git a/include/tcg/tcg.h b/include/tcg/tcg.h index ece94bf58b..70fc399d51 100644 --- a/include/tcg/tcg.h +++ b/include/tcg/tcg.h @@ -407,6 +407,13 @@ struct TCGContext { /* Threshold to flush the translated code buffer. */ void *code_gen_highwater; +#ifdef XBOX + /* Hot arena for tier-1 TBs — keeps hot code packed within L1I. */ + void *hot_arena_start; + void *hot_arena_end; + void *hot_arena_ptr; +#endif + /* Track which vCPU triggers events */ CPUState *cpu; /* *_trans */ @@ -466,6 +473,21 @@ struct TCGContext { sigjmp_buf jmp_trans; void *disas_ctx; + +#ifdef XBOX + /* + * When true, translator_loop suppresses gen_tb_start() and + * gen_tb_end() so that a second call to translate_code() appends + * IR to the existing ops list (used for superblock formation). + */ + bool superblock_append; + /* + * For cross-TB dead flag elimination: cc_defines_first mask of + * each known successor TB (exit 0 and exit 1). Populated in + * translate-all.c before tcg_gen_code; consumed in tier1-opt.c. + */ + uint8_t succ_cc_defines[2]; +#endif }; static inline bool temp_readonly(TCGTemp *ts) @@ -484,6 +506,18 @@ extern const void *tcg_code_gen_epilogue; extern uintptr_t tcg_splitwx_diff; extern TCGv_env tcg_env; +#ifdef XBOX +typedef struct { + uintptr_t host_base; /* offset 0: host pointer to guest RAM */ + volatile int32_t active; /* offset 8: 1 = fast path usable (boot done, no callbacks) */ + volatile int32_t cb_count; /* offset 12: >0 when mem-access callbacks active */ + uintptr_t vram_pci_base; /* offset 16: NV2A VRAM PCI BAR address, 0 = not configured */ +} XboxRamFPState; + +extern XboxRamFPState xbox_ram_fp; +extern uint64_t xbox_ram_size; +#endif + bool in_code_gen_buffer(const void *p); #ifdef CONFIG_DEBUG_TCG @@ -694,6 +728,9 @@ static inline bool tcg_op_buf_full(void) void *tcg_malloc_internal(TCGContext *s, int size); void tcg_pool_reset(TCGContext *s); TranslationBlock *tcg_tb_alloc(TCGContext *s); +#ifdef XBOX +TranslationBlock *tcg_tb_alloc_hot(TCGContext *s); +#endif void tcg_region_reset_all(void); diff --git a/include/xemu-config.h b/include/xemu-config.h index 9a01c7ccd8..eda1fada0e 100644 --- a/include/xemu-config.h +++ b/include/xemu-config.h @@ -286,8 +286,9 @@ struct config { } sys; struct perf { - bool hard_fpu; + bool fp_jit; bool cache_shaders; + bool unlock_framerate; } perf; }; diff --git a/roms/ipxe b/roms/ipxe index 4bd064de23..6d2f69f55e 160000 --- a/roms/ipxe +++ b/roms/ipxe @@ -1 +1 @@ -Subproject commit 4bd064de239dab2426b31c9789a1f4d78087dc63 +Subproject commit 6d2f69f55e6832efb0646df40244d6225dd1e13e diff --git a/roms/openbios b/roms/openbios index c3a19c1e54..e7fd10c0ff 160000 --- a/roms/openbios +++ b/roms/openbios @@ -1 +1 @@ -Subproject commit c3a19c1e54977a53027d6232050e1e3e39a98a1b +Subproject commit e7fd10c0ff40b3bb6752c861b60afcf1cdf0a035 diff --git a/roms/opensbi b/roms/opensbi index a32a910691..4813a20420 160000 --- a/roms/opensbi +++ b/roms/opensbi @@ -1 +1 @@ -Subproject commit a32a91069119e7a5aa31e6bc51d5e00860be3d80 +Subproject commit 4813a2042096b7860655761aad973723293a552e diff --git a/roms/seabios b/roms/seabios index b52ca86e09..106549a436 160000 --- a/roms/seabios +++ b/roms/seabios @@ -1 +1 @@ -Subproject commit b52ca86e094d19b58e2304417787e96b940e39c6 +Subproject commit 106549a4362f6b499da522f8f8f5ed9f98388f87 diff --git a/roms/seabios-hppa b/roms/seabios-hppa index a04b6f35c0..cf3a472f4d 160000 --- a/roms/seabios-hppa +++ b/roms/seabios-hppa @@ -1 +1 @@ -Subproject commit a04b6f35c0b7437ce968f64106ee6ff5aadee419 +Subproject commit cf3a472f4ddea30d2d483a8ba4b005505f0dabb3 diff --git a/roms/skiboot b/roms/skiboot index 785a5e3070..ecebf4fb3d 160000 --- a/roms/skiboot +++ b/roms/skiboot @@ -1 +1 @@ -Subproject commit 785a5e3070a86e18521e62fe202b87209de30fa2 +Subproject commit ecebf4fb3d9ca19b0f3c48a3595ac402c731f38f diff --git a/roms/vbootrom b/roms/vbootrom index 183c9ff805..1c8e9510b2 160000 --- a/roms/vbootrom +++ b/roms/vbootrom @@ -1 +1 @@ -Subproject commit 183c9ff8056b7946db1ae49cc23e8980ac413174 +Subproject commit 1c8e9510b22c5b0fd7d7753f08042a4bcbd2939d diff --git a/system/physmem.c b/system/physmem.c index 1e003c2a12..9db4702227 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -891,6 +891,15 @@ MemAccessCallback *mem_access_callback_insert(CPUState *cpu, MemoryRegion *mr, cb->func = func; cb->opaque = opaque; +#ifdef XBOX + { + extern volatile int32_t *xbox_ram_fp_cb_count_ptr; + if (xbox_ram_fp_cb_count_ptr) { + qatomic_inc(xbox_ram_fp_cb_count_ptr); + } + } +#endif + async_safe_run_on_cpu(cpu, do_mem_access_callback_insert, RUN_ON_CPU_HOST_PTR(cb)); @@ -914,6 +923,15 @@ void mem_access_callback_remove_by_ref(CPUState *cpu, MemAccessCallback *cb) return; } +#ifdef XBOX + { + extern volatile int32_t *xbox_ram_fp_cb_count_ptr; + if (xbox_ram_fp_cb_count_ptr) { + qatomic_fetch_dec(xbox_ram_fp_cb_count_ptr); + } + } +#endif + async_safe_run_on_cpu(cpu, do_mem_access_callback_remove_by_ref, RUN_ON_CPU_HOST_PTR(cb)); diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 9b6b086e8f..b908b3f22d 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1634,6 +1634,7 @@ typedef struct BNDCSReg { typedef union { floatx80 d __attribute__((aligned(16))); MMXReg mmx; + double native_d; } FPReg; typedef struct { @@ -1901,6 +1902,7 @@ typedef struct CPUArchState { /* emulator internal variables */ float_status fp_status; floatx80 ft0; + double ft0_native; float_status mmx_status; /* for 3DNow! float ops */ float_status sse_status; diff --git a/target/i386/helper.h b/target/i386/helper.h index dde92f246d..84a44cb760 100644 --- a/target/i386/helper.h +++ b/target/i386/helper.h @@ -101,7 +101,7 @@ DEF_HELPER_FLAGS_3(write_crN, TCG_CALL_NO_RWG, void, env, int, tl) #endif /* !CONFIG_USER_ONLY */ /* x86 FPU */ -#if defined(XBOX) && defined(__x86_64__) +#if defined(XBOX) && (defined(__x86_64__) || defined(__aarch64__)) #define HS_DEF_HELPER_1(name, ret, t1) \ DEF_HELPER_1(name ## __soft, ret, t1) \ DEF_HELPER_1(name ## __hard, ret, t1) diff --git a/target/i386/ops_fpu.h b/target/i386/ops_fpu.h index 5f79f50412..932300d9e2 100644 --- a/target/i386/ops_fpu.h +++ b/target/i386/ops_fpu.h @@ -24,9 +24,33 @@ #define tcg_temp_new_fp glue(tcg_temp_new_, fPREC) #define tcg_gen_st80f_fp glue(tcg_gen_st80f, PREC_SUFFIX) #define tcg_gen_ld80f_fp glue(tcg_gen_ld80f, PREC_SUFFIX) +#define nds_load_fp glue(nds_load_fp, PREC_SUFFIX) +#define nds_store_fp glue(nds_store_fp, PREC_SUFFIX) #define get_ft0 glue(get_ft0, PREC_SUFFIX) +#define alloc_ft0 glue(alloc_ft0, PREC_SUFFIX) #define get_stn glue(get_stn, PREC_SUFFIX) +#define alloc_stn glue(alloc_stn, PREC_SUFFIX) #define get_st0 glue(get_st0, PREC_SUFFIX) +#define alloc_st0 glue(alloc_st0, PREC_SUFFIX) + +static void glue(nds_load_fp, PREC_SUFFIX)(PREC_TYPE dst, TCGv_ptr p) +{ +#if defined(__aarch64__) + if (g_use_fp_jit) { + TCGv_i64 raw = tcg_temp_new_i64(); + tcg_gen_ld_i64(raw, p, 0); +#if PREC == 64 + tcg_gen_mov64i_f64(dst, raw); +#else + TCGv_f64 tmp = tcg_temp_new_f64(); + tcg_gen_mov64i_f64(tmp, raw); + tcg_gen_cvt64f_f32(dst, tmp); +#endif + return; + } +#endif + tcg_gen_ld80f_fp(dst, p); +} static PREC_TYPE get_ft0(DisasContext *s) { @@ -37,7 +61,24 @@ static PREC_TYPE get_ft0(DisasContext *s) if (*v == NULL) { *v = tcg_temp_new_fp(); TCGv_ptr p = gen_ft0_ptr(); - tcg_gen_ld80f_fp(*v, p); + glue(nds_load_fp, PREC_SUFFIX)(*v, p); + } + + return *v; +} + +/* + * Allocate FT0 temp without loading the current value from memory. + * Use when the caller will immediately overwrite the value. + */ +static PREC_TYPE alloc_ft0(DisasContext *s) +{ + gen_flcr(s); + + PREC_TYPE *v = (PREC_TYPE *)&s->ft0; + + if (*v == NULL) { + *v = tcg_temp_new_fp(); } return *v; @@ -53,7 +94,25 @@ static PREC_TYPE get_stn(DisasContext *s, int opreg) if (*t == NULL) { *t = tcg_temp_new_fp(); TCGv_ptr p = gen_stn_ptr(opreg); - tcg_gen_ld80f_fp(*t, p); + glue(nds_load_fp, PREC_SUFFIX)(*t, p); + } + + return *t; +} + +/* + * Allocate STn temp without loading the current value from memory. + * Use when the caller will immediately overwrite the value. + */ +static PREC_TYPE alloc_stn(DisasContext *s, int opreg) +{ + assert(!(opreg & ~7)); + gen_flcr(s); + + PREC_TYPE *t = (PREC_TYPE *)&s->fpregs[(s->fpstt_delta + opreg) & 7]; + + if (*t == NULL) { + *t = tcg_temp_new_fp(); } return *t; @@ -64,20 +123,44 @@ static PREC_TYPE get_st0(DisasContext *s) return get_stn(s, 0); } +static PREC_TYPE alloc_st0(DisasContext *s) +{ + return alloc_stn(s, 0); +} + +static void glue(nds_store_fp, PREC_SUFFIX)(PREC_TYPE src, TCGv_ptr p) +{ +#if defined(__aarch64__) + if (g_use_fp_jit) { + TCGv_i64 raw = tcg_temp_new_i64(); +#if PREC == 64 + tcg_gen_mov64f_i64(raw, src); +#else + TCGv_f64 tmp = tcg_temp_new_f64(); + tcg_gen_cvt32f_f64(tmp, src); + tcg_gen_mov64f_i64(raw, tmp); +#endif + tcg_gen_st_i64(raw, p, 0); + return; + } +#endif + tcg_gen_st80f_fp(src, p); +} + static void glue(flush_fp_regs, PREC_SUFFIX)(DisasContext *s) { for (int i = 0; i < 8; i++) { PREC_TYPE *t = (PREC_TYPE *)&s->fpregs[(s->fpstt_delta + i) & 7]; if (*t) { TCGv_ptr ptr = gen_stn_ptr(i); - tcg_gen_st80f_fp(*t, ptr); + glue(nds_store_fp, PREC_SUFFIX)(*t, ptr); *t = NULL; } } if (s->ft0) { TCGv_ptr ptr = gen_ft0_ptr(); - tcg_gen_st80f_fp((PREC_TYPE)s->ft0, ptr); + glue(nds_store_fp, PREC_SUFFIX)((PREC_TYPE)s->ft0, ptr); s->ft0 = NULL; } } @@ -194,52 +277,52 @@ static void glue(gen_helper_fp_arith_STN_ST0, PREC_SUFFIX)(DisasContext *s, static void glue(gen_fmov_FT0_STN, PREC_SUFFIX)(DisasContext *s, int st_index) { - glue(tcg_gen_mov, PREC_SUFFIX)(get_ft0(s), get_stn(s, st_index)); + glue(tcg_gen_mov, PREC_SUFFIX)(alloc_ft0(s), get_stn(s, st_index)); } static void glue(gen_fmov_ST0_STN, PREC_SUFFIX)(DisasContext *s, int st_index) { - glue(tcg_gen_mov, PREC_SUFFIX)(get_st0(s), get_stn(s, st_index)); + glue(tcg_gen_mov, PREC_SUFFIX)(alloc_st0(s), get_stn(s, st_index)); } static void glue(gen_fmov_STN_ST0, PREC_SUFFIX)(DisasContext *s, int st_index) { - glue(tcg_gen_mov, PREC_SUFFIX)(get_stn(s, st_index), get_st0(s)); + glue(tcg_gen_mov, PREC_SUFFIX)(alloc_stn(s, st_index), get_st0(s)); } static void glue(gen_flds_FT0, PREC_SUFFIX)(DisasContext *s, TCGv_i32 arg) { - glue(gen_mov32i, PREC_SUFFIX)(get_ft0(s), arg); + glue(gen_mov32i, PREC_SUFFIX)(alloc_ft0(s), arg); } static void glue(gen_flds_ST0, PREC_SUFFIX)(DisasContext *s, TCGv_i32 arg) { - glue(gen_mov32i, PREC_SUFFIX)(get_st0(s), arg); + glue(gen_mov32i, PREC_SUFFIX)(alloc_st0(s), arg); } static void glue(gen_fldl_FT0, PREC_SUFFIX)(DisasContext *s, TCGv_i64 arg) { - glue(gen_mov64i, PREC_SUFFIX)(get_ft0(s), arg); + glue(gen_mov64i, PREC_SUFFIX)(alloc_ft0(s), arg); } static void glue(gen_fldl_ST0, PREC_SUFFIX)(DisasContext *s, TCGv_i64 arg) { - glue(gen_mov64i, PREC_SUFFIX)(get_st0(s), arg); + glue(gen_mov64i, PREC_SUFFIX)(alloc_st0(s), arg); } static void glue(gen_fildl_FT0, PREC_SUFFIX)(DisasContext *s, TCGv_i32 arg) { - glue(tcg_gen_cvt32i, PREC_SUFFIX)(get_ft0(s), arg); + glue(tcg_gen_cvt32i, PREC_SUFFIX)(alloc_ft0(s), arg); } static void glue(gen_fildl_ST0, PREC_SUFFIX)(DisasContext *s, TCGv_i32 arg) { - glue(tcg_gen_cvt32i, PREC_SUFFIX)(get_st0(s), arg); + glue(tcg_gen_cvt32i, PREC_SUFFIX)(alloc_st0(s), arg); } static void glue(gen_fildll_ST0, PREC_SUFFIX)(DisasContext *s, TCGv_i64 arg) { - glue(tcg_gen_cvt64i, PREC_SUFFIX)(get_st0(s), arg); + glue(tcg_gen_cvt64i, PREC_SUFFIX)(alloc_st0(s), arg); } static void glue(gen_fistl_ST0, PREC_SUFFIX)(DisasContext *s, TCGv_i32 arg) @@ -294,15 +377,15 @@ static void glue(gen_fcos, PREC_SUFFIX)(DisasContext *s) static void glue(gen_fld1_ST0, PREC_SUFFIX)(DisasContext *s) { - glue(gen_movi, PREC_SUFFIX)(s, get_st0(s), 1.0); + glue(gen_movi, PREC_SUFFIX)(s, alloc_st0(s), 1.0); } static void glue(gen_fldz_ST0, PREC_SUFFIX)(DisasContext *s) { - glue(gen_movi, PREC_SUFFIX)(s, get_st0(s), 0.0); + glue(gen_movi, PREC_SUFFIX)(s, alloc_st0(s), 0.0); } static void glue(gen_fldz_FT0, PREC_SUFFIX)(DisasContext *s) { - glue(gen_movi, PREC_SUFFIX)(s, get_ft0(s), 0.0); + glue(gen_movi, PREC_SUFFIX)(s, alloc_ft0(s), 0.0); } diff --git a/target/i386/ops_sse.h b/target/i386/ops_sse.h index a2e4d48039..745b339eea 100644 --- a/target/i386/ops_sse.h +++ b/target/i386/ops_sse.h @@ -22,6 +22,154 @@ #include "crypto/aes-round.h" #include "crypto/clmul.h" +/* + * ARM64 native float helpers for SSE operations. + * + * On ARM64, replace softfloat per-element calls with native FP instructions. + * This trades exact IEEE exception tracking for significant performance gains. + * Xbox games do not check MXCSR exception flags so this is safe for xemu. + */ +#ifndef XEMU_OPT_NATIVE_FLOAT +#define XEMU_OPT_NATIVE_FLOAT 1 +#endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && !defined(OPS_SSE_NATIVE_FP_DEFINED) +#define OPS_SSE_NATIVE_FP_DEFINED + +#include +#include + +static inline float32 float32_add_native(float32 a, float32 b) +{ + float fa, fb, fr; + memcpy(&fa, &a, sizeof(fa)); + memcpy(&fb, &b, sizeof(fb)); + fr = fa + fb; + float32 r; + memcpy(&r, &fr, sizeof(r)); + return r; +} + +static inline float32 float32_sub_native(float32 a, float32 b) +{ + float fa, fb, fr; + memcpy(&fa, &a, sizeof(fa)); + memcpy(&fb, &b, sizeof(fb)); + fr = fa - fb; + float32 r; + memcpy(&r, &fr, sizeof(r)); + return r; +} + +static inline float32 float32_mul_native(float32 a, float32 b) +{ + float fa, fb, fr; + memcpy(&fa, &a, sizeof(fa)); + memcpy(&fb, &b, sizeof(fb)); + fr = fa * fb; + float32 r; + memcpy(&r, &fr, sizeof(r)); + return r; +} + +static inline float32 float32_div_native(float32 a, float32 b) +{ + float fa, fb, fr; + memcpy(&fa, &a, sizeof(fa)); + memcpy(&fb, &b, sizeof(fb)); + fr = fa / fb; + float32 r; + memcpy(&r, &fr, sizeof(r)); + return r; +} + +static inline float64 float64_add_native(float64 a, float64 b) +{ + double da, db, dr; + memcpy(&da, &a, sizeof(da)); + memcpy(&db, &b, sizeof(db)); + dr = da + db; + float64 r; + memcpy(&r, &dr, sizeof(r)); + return r; +} + +static inline float64 float64_sub_native(float64 a, float64 b) +{ + double da, db, dr; + memcpy(&da, &a, sizeof(da)); + memcpy(&db, &b, sizeof(db)); + dr = da - db; + float64 r; + memcpy(&r, &dr, sizeof(r)); + return r; +} + +static inline float64 float64_mul_native(float64 a, float64 b) +{ + double da, db, dr; + memcpy(&da, &a, sizeof(da)); + memcpy(&db, &b, sizeof(db)); + dr = da * db; + float64 r; + memcpy(&r, &dr, sizeof(r)); + return r; +} + +static inline float64 float64_div_native(float64 a, float64 b) +{ + double da, db, dr; + memcpy(&da, &a, sizeof(da)); + memcpy(&db, &b, sizeof(db)); + dr = da / db; + float64 r; + memcpy(&r, &dr, sizeof(r)); + return r; +} + +static inline float32 float32_sqrt_native(float32 a) +{ + float fa, fr; + memcpy(&fa, &a, sizeof(fa)); + fr = sqrtf(fa); + float32 r; + memcpy(&r, &fr, sizeof(r)); + return r; +} + +static inline float64 float64_sqrt_native(float64 a) +{ + double da, dr; + memcpy(&da, &a, sizeof(da)); + dr = sqrt(da); + float64 r; + memcpy(&r, &dr, sizeof(r)); + return r; +} + +static inline FloatRelation float32_compare_native(float32 a, float32 b) +{ + float fa, fb; + memcpy(&fa, &a, sizeof(fa)); + memcpy(&fb, &b, sizeof(fb)); + if (__builtin_isunordered(fa, fb)) return float_relation_unordered; + if (fa < fb) return float_relation_less; + if (fa == fb) return float_relation_equal; + return float_relation_greater; +} + +static inline FloatRelation float64_compare_native(float64 a, float64 b) +{ + double da, db; + memcpy(&da, &a, sizeof(da)); + memcpy(&db, &b, sizeof(db)); + if (__builtin_isunordered(da, db)) return float_relation_unordered; + if (da < db) return float_relation_less; + if (da == db) return float_relation_equal; + return float_relation_greater; +} + +#endif /* XBOX && __aarch64__ && !OPS_SSE_NATIVE_FP_DEFINED */ + #if SHIFT == 0 #define Reg MMXReg #define XMM_ONLY(...) @@ -511,32 +659,241 @@ void glue(helper_pshufhw, SUFFIX)(Reg *d, Reg *s, int order) #endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT +#define FPU_ADD(size, a, b) float ## size ## _add_native(a, b) +#define FPU_SUB(size, a, b) float ## size ## _sub_native(a, b) +#define FPU_MUL(size, a, b) float ## size ## _mul_native(a, b) +#define FPU_DIV(size, a, b) float ## size ## _div_native(a, b) +#else #define FPU_ADD(size, a, b) float ## size ## _add(a, b, &env->sse_status) #define FPU_SUB(size, a, b) float ## size ## _sub(a, b, &env->sse_status) #define FPU_MUL(size, a, b) float ## size ## _mul(a, b, &env->sse_status) #define FPU_DIV(size, a, b) float ## size ## _div(a, b, &env->sse_status) +#endif /* Note that the choice of comparison op here is important to get the * special cases right: for min and max Intel specifies that (-0,0), * (NaN, anything) and (anything, NaN) return the second argument. */ +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT +#define FPU_MIN(size, a, b) \ + (float ## size ## _compare_native(a, b) == float_relation_less ? (a) : (b)) +#define FPU_MAX(size, a, b) \ + (float ## size ## _compare_native(b, a) == float_relation_less ? (a) : (b)) +#else #define FPU_MIN(size, a, b) \ (float ## size ## _lt(a, b, &env->sse_status) ? (a) : (b)) #define FPU_MAX(size, a, b) \ (float ## size ## _lt(b, a, &env->sse_status) ? (a) : (b)) +#endif +/* + * On ARM64 with SHIFT==1 (XMM 128-bit), use NEON intrinsics for packed + * float ops to process all 4 (PS) or 2 (PD) elements in a single + * vector instruction instead of looping per-element. + */ +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +#include + +void glue(helper_addps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vaddq_f32(va, vs)); +} + +void glue(helper_addpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vaddq_f64(va, vs)); +} + +void helper_addss(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_S(0) = FPU_ADD(32, v->ZMM_S(0), s->ZMM_S(0)); + for (int i = 1; i < 4; i++) { + d->ZMM_L(i) = v->ZMM_L(i); + } +} + +void helper_addsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_D(0) = FPU_ADD(64, v->ZMM_D(0), s->ZMM_D(0)); + d->ZMM_Q(1) = v->ZMM_Q(1); +} + +void glue(helper_subps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vsubq_f32(va, vs)); +} + +void glue(helper_subpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vsubq_f64(va, vs)); +} + +void helper_subss(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_S(0) = FPU_SUB(32, v->ZMM_S(0), s->ZMM_S(0)); + for (int i = 1; i < 4; i++) { + d->ZMM_L(i) = v->ZMM_L(i); + } +} + +void helper_subsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_D(0) = FPU_SUB(64, v->ZMM_D(0), s->ZMM_D(0)); + d->ZMM_Q(1) = v->ZMM_Q(1); +} + +void glue(helper_mulps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vmulq_f32(va, vs)); +} + +void glue(helper_mulpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vmulq_f64(va, vs)); +} + +void helper_mulss(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_S(0) = FPU_MUL(32, v->ZMM_S(0), s->ZMM_S(0)); + for (int i = 1; i < 4; i++) { + d->ZMM_L(i) = v->ZMM_L(i); + } +} + +void helper_mulsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_D(0) = FPU_MUL(64, v->ZMM_D(0), s->ZMM_D(0)); + d->ZMM_Q(1) = v->ZMM_Q(1); +} + +void glue(helper_divps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vdivq_f32(va, vs)); +} + +void glue(helper_divpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vdivq_f64(va, vs)); +} + +void helper_divss(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_S(0) = FPU_DIV(32, v->ZMM_S(0), s->ZMM_S(0)); + for (int i = 1; i < 4; i++) { + d->ZMM_L(i) = v->ZMM_L(i); + } +} + +void helper_divsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_D(0) = FPU_DIV(64, v->ZMM_D(0), s->ZMM_D(0)); + d->ZMM_Q(1) = v->ZMM_Q(1); +} + +void glue(helper_minps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vminq_f32(va, vs)); +} + +void glue(helper_minpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vminq_f64(va, vs)); +} + +void helper_minss(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_S(0) = FPU_MIN(32, v->ZMM_S(0), s->ZMM_S(0)); + for (int i = 1; i < 4; i++) { + d->ZMM_L(i) = v->ZMM_L(i); + } +} + +void helper_minsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_D(0) = FPU_MIN(64, v->ZMM_D(0), s->ZMM_D(0)); + d->ZMM_Q(1) = v->ZMM_Q(1); +} + +void glue(helper_maxps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vmaxq_f32(va, vs)); +} + +void glue(helper_maxpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vmaxq_f64(va, vs)); +} + +void helper_maxss(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_S(0) = FPU_MAX(32, v->ZMM_S(0), s->ZMM_S(0)); + for (int i = 1; i < 4; i++) { + d->ZMM_L(i) = v->ZMM_L(i); + } +} + +void helper_maxsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + d->ZMM_D(0) = FPU_MAX(64, v->ZMM_D(0), s->ZMM_D(0)); + d->ZMM_Q(1) = v->ZMM_Q(1); +} + +#else SSE_HELPER_S(add, FPU_ADD) SSE_HELPER_S(sub, FPU_SUB) SSE_HELPER_S(mul, FPU_MUL) SSE_HELPER_S(div, FPU_DIV) SSE_HELPER_S(min, FPU_MIN) SSE_HELPER_S(max, FPU_MAX) +#endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_sqrtps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) +{ + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + vst1q_f32((float *)&d->ZMM_S(0), vsqrtq_f32(vs)); +} + +void glue(helper_sqrtpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) +{ + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + vst1q_f64((double *)&d->ZMM_D(0), vsqrtq_f64(vs)); +} +#else void glue(helper_sqrtps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) { int i; for (i = 0; i < 2 << SHIFT; i++) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + d->ZMM_S(i) = float32_sqrt_native(s->ZMM_S(i)); +#else d->ZMM_S(i) = float32_sqrt(s->ZMM_S(i), &env->sse_status); +#endif } } @@ -544,15 +901,24 @@ void glue(helper_sqrtpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) { int i; for (i = 0; i < 1 << SHIFT; i++) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + d->ZMM_D(i) = float64_sqrt_native(s->ZMM_D(i)); +#else d->ZMM_D(i) = float64_sqrt(s->ZMM_D(i), &env->sse_status); +#endif } } +#endif #if SHIFT == 1 void helper_sqrtss(CPUX86State *env, Reg *d, Reg *v, Reg *s) { int i; +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + d->ZMM_S(0) = float32_sqrt_native(s->ZMM_S(0)); +#else d->ZMM_S(0) = float32_sqrt(s->ZMM_S(0), &env->sse_status); +#endif for (i = 1; i < 2 << SHIFT; i++) { d->ZMM_L(i) = v->ZMM_L(i); } @@ -561,7 +927,11 @@ void helper_sqrtss(CPUX86State *env, Reg *d, Reg *v, Reg *s) void helper_sqrtsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) { int i; +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + d->ZMM_D(0) = float64_sqrt_native(s->ZMM_D(0)); +#else d->ZMM_D(0) = float64_sqrt(s->ZMM_D(0), &env->sse_status); +#endif for (i = 1; i < 1 << SHIFT; i++) { d->ZMM_Q(i) = v->ZMM_Q(i); } @@ -569,6 +939,23 @@ void helper_sqrtsd(CPUX86State *env, Reg *d, Reg *v, Reg *s) #endif /* float to float conversions */ +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_cvtps2pd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) +{ + float32x2_t lo = vld1_f32((const float *)&s->ZMM_S(0)); + float64x2_t hi_d = vcvt_f64_f32(vget_high_f32(vld1q_f32((const float *)&s->ZMM_S(0)))); + float64x2_t lo_d = vcvt_f64_f32(lo); + vst1q_f64((double *)&d->ZMM_D(0), lo_d); +} + +void glue(helper_cvtpd2ps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) +{ + float64x2_t vd = vld1q_f64((const double *)&s->ZMM_D(0)); + float32x2_t vs = vcvt_f32_f64(vd); + vst1_f32((float *)&d->ZMM_S(0), vs); + d->Q(1) = 0; +} +#else void glue(helper_cvtps2pd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) { int i; @@ -587,6 +974,7 @@ void glue(helper_cvtpd2ps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) d->Q(i) = 0; } } +#endif #if SHIFT >= 1 void glue(helper_cvtph2ps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) @@ -621,7 +1009,14 @@ void glue(helper_cvtps2ph, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, int mode) void helper_cvtss2sd(CPUX86State *env, Reg *d, Reg *v, Reg *s) { int i; +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f; + memcpy(&f, &s->ZMM_S(0), sizeof(f)); + double dd = (double)f; + memcpy(&d->ZMM_D(0), &dd, sizeof(dd)); +#else d->ZMM_D(0) = float32_to_float64(s->ZMM_S(0), &env->sse_status); +#endif for (i = 1; i < 1 << SHIFT; i++) { d->ZMM_Q(i) = v->ZMM_Q(i); } @@ -630,7 +1025,14 @@ void helper_cvtss2sd(CPUX86State *env, Reg *d, Reg *v, Reg *s) void helper_cvtsd2ss(CPUX86State *env, Reg *d, Reg *v, Reg *s) { int i; +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double dd; + memcpy(&dd, &s->ZMM_D(0), sizeof(dd)); + float f = (float)dd; + memcpy(&d->ZMM_S(0), &f, sizeof(f)); +#else d->ZMM_S(0) = float64_to_float32(s->ZMM_D(0), &env->sse_status); +#endif for (i = 1; i < 2 << SHIFT; i++) { d->ZMM_L(i) = v->ZMM_L(i); } @@ -638,6 +1040,24 @@ void helper_cvtsd2ss(CPUX86State *env, Reg *d, Reg *v, Reg *s) #endif /* integer to float */ +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_cvtdq2ps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) +{ + int32x4_t vi = vld1q_s32((const int32_t *)&s->ZMM_L(0)); + float32x4_t vf = vcvtq_f32_s32(vi); + vst1q_f32((float *)&d->ZMM_S(0), vf); +} + +void glue(helper_cvtdq2pd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) +{ + int32_t i0 = s->ZMM_L(0); + int32_t i1 = s->ZMM_L(1); + double d0 = (double)i0; + double d1 = (double)i1; + memcpy(&d->ZMM_D(0), &d0, sizeof(d0)); + memcpy(&d->ZMM_D(1), &d1, sizeof(d1)); +} +#else void glue(helper_cvtdq2ps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) { int i; @@ -654,28 +1074,53 @@ void glue(helper_cvtdq2pd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) d->ZMM_D(i) = int32_to_float64(l, &env->sse_status); } } +#endif #if SHIFT == 1 void helper_cvtpi2ps(CPUX86State *env, ZMMReg *d, MMXReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f0 = (float)(int32_t)s->MMX_L(0); + float f1 = (float)(int32_t)s->MMX_L(1); + memcpy(&d->ZMM_S(0), &f0, sizeof(f0)); + memcpy(&d->ZMM_S(1), &f1, sizeof(f1)); +#else d->ZMM_S(0) = int32_to_float32(s->MMX_L(0), &env->sse_status); d->ZMM_S(1) = int32_to_float32(s->MMX_L(1), &env->sse_status); +#endif } void helper_cvtpi2pd(CPUX86State *env, ZMMReg *d, MMXReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double d0 = (double)(int32_t)s->MMX_L(0); + double d1 = (double)(int32_t)s->MMX_L(1); + memcpy(&d->ZMM_D(0), &d0, sizeof(d0)); + memcpy(&d->ZMM_D(1), &d1, sizeof(d1)); +#else d->ZMM_D(0) = int32_to_float64(s->MMX_L(0), &env->sse_status); d->ZMM_D(1) = int32_to_float64(s->MMX_L(1), &env->sse_status); +#endif } void helper_cvtsi2ss(CPUX86State *env, ZMMReg *d, uint32_t val) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f = (float)(int32_t)val; + memcpy(&d->ZMM_S(0), &f, sizeof(f)); +#else d->ZMM_S(0) = int32_to_float32(val, &env->sse_status); +#endif } void helper_cvtsi2sd(CPUX86State *env, ZMMReg *d, uint32_t val) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double dd = (double)(int32_t)val; + memcpy(&d->ZMM_D(0), &dd, sizeof(dd)); +#else d->ZMM_D(0) = int32_to_float64(val, &env->sse_status); +#endif } #ifdef TARGET_X86_64 @@ -727,6 +1172,27 @@ WRAP_FLOATCONV(int64_t, float64_to_int64, float64, INT64_MIN) WRAP_FLOATCONV(int64_t, float64_to_int64_round_to_zero, float64, INT64_MIN) #endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_cvtps2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) +{ + float32x4_t vf = vld1q_f32((const float *)&s->ZMM_S(0)); + int32x4_t vi = vcvtnq_s32_f32(vf); + /* x86 returns INT32_MIN for NaN; ARM64 returns 0 */ + uint32x4_t nan_mask = vmvnq_u32(vceqq_f32(vf, vf)); + vi = vbslq_s32(nan_mask, vdupq_n_s32(INT32_MIN), vi); + vst1q_s32((int32_t *)&d->ZMM_L(0), vi); +} + +void glue(helper_cvtpd2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) +{ + double d0, d1; + memcpy(&d0, &s->ZMM_D(0), sizeof(d0)); + memcpy(&d1, &s->ZMM_D(1), sizeof(d1)); + d->ZMM_L(0) = __builtin_isnan(d0) ? INT32_MIN : (int32_t)lrint(d0); + d->ZMM_L(1) = __builtin_isnan(d1) ? INT32_MIN : (int32_t)lrint(d1); + d->Q(1) = 0; +} +#else void glue(helper_cvtps2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) { int i; @@ -745,28 +1211,59 @@ void glue(helper_cvtpd2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) d->Q(i) = 0; } } +#endif #if SHIFT == 1 void helper_cvtps2pi(CPUX86State *env, MMXReg *d, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f; + memcpy(&f, &s->ZMM_S(0), sizeof(f)); + d->MMX_L(0) = __builtin_isnan(f) ? INT32_MIN : (int32_t)lrintf(f); + memcpy(&f, &s->ZMM_S(1), sizeof(f)); + d->MMX_L(1) = __builtin_isnan(f) ? INT32_MIN : (int32_t)lrintf(f); +#else d->MMX_L(0) = x86_float32_to_int32(s->ZMM_S(0), &env->sse_status); d->MMX_L(1) = x86_float32_to_int32(s->ZMM_S(1), &env->sse_status); +#endif } void helper_cvtpd2pi(CPUX86State *env, MMXReg *d, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double dd; + memcpy(&dd, &s->ZMM_D(0), sizeof(dd)); + d->MMX_L(0) = __builtin_isnan(dd) ? INT32_MIN : (int32_t)lrint(dd); + memcpy(&dd, &s->ZMM_D(1), sizeof(dd)); + d->MMX_L(1) = __builtin_isnan(dd) ? INT32_MIN : (int32_t)lrint(dd); +#else d->MMX_L(0) = x86_float64_to_int32(s->ZMM_D(0), &env->sse_status); d->MMX_L(1) = x86_float64_to_int32(s->ZMM_D(1), &env->sse_status); +#endif } int32_t helper_cvtss2si(CPUX86State *env, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f; + memcpy(&f, &s->ZMM_S(0), sizeof(f)); + if (__builtin_isnan(f)) return INT32_MIN; + return (int32_t)lrintf(f); +#else return x86_float32_to_int32(s->ZMM_S(0), &env->sse_status); +#endif } int32_t helper_cvtsd2si(CPUX86State *env, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double dd; + memcpy(&dd, &s->ZMM_D(0), sizeof(dd)); + if (__builtin_isnan(dd)) return INT32_MIN; + return (int32_t)lrint(dd); +#else return x86_float64_to_int32(s->ZMM_D(0), &env->sse_status); +#endif } #ifdef TARGET_X86_64 @@ -783,6 +1280,26 @@ int64_t helper_cvtsd2sq(CPUX86State *env, ZMMReg *s) #endif /* float to integer truncated */ +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_cvttps2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) +{ + float32x4_t vf = vld1q_f32((const float *)&s->ZMM_S(0)); + int32x4_t vi = vcvtq_s32_f32(vf); + uint32x4_t nan_mask = vmvnq_u32(vceqq_f32(vf, vf)); + vi = vbslq_s32(nan_mask, vdupq_n_s32(INT32_MIN), vi); + vst1q_s32((int32_t *)&d->ZMM_L(0), vi); +} + +void glue(helper_cvttpd2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) +{ + double d0, d1; + memcpy(&d0, &s->ZMM_D(0), sizeof(d0)); + memcpy(&d1, &s->ZMM_D(1), sizeof(d1)); + d->ZMM_L(0) = __builtin_isnan(d0) ? INT32_MIN : (int32_t)d0; + d->ZMM_L(1) = __builtin_isnan(d1) ? INT32_MIN : (int32_t)d1; + d->Q(1) = 0; +} +#else void glue(helper_cvttps2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) { int i; @@ -803,28 +1320,59 @@ void glue(helper_cvttpd2dq, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) d->Q(i) = 0; } } +#endif #if SHIFT == 1 void helper_cvttps2pi(CPUX86State *env, MMXReg *d, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f; + memcpy(&f, &s->ZMM_S(0), sizeof(f)); + d->MMX_L(0) = __builtin_isnan(f) ? INT32_MIN : (int32_t)f; + memcpy(&f, &s->ZMM_S(1), sizeof(f)); + d->MMX_L(1) = __builtin_isnan(f) ? INT32_MIN : (int32_t)f; +#else d->MMX_L(0) = x86_float32_to_int32_round_to_zero(s->ZMM_S(0), &env->sse_status); d->MMX_L(1) = x86_float32_to_int32_round_to_zero(s->ZMM_S(1), &env->sse_status); +#endif } void helper_cvttpd2pi(CPUX86State *env, MMXReg *d, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double dd; + memcpy(&dd, &s->ZMM_D(0), sizeof(dd)); + d->MMX_L(0) = __builtin_isnan(dd) ? INT32_MIN : (int32_t)dd; + memcpy(&dd, &s->ZMM_D(1), sizeof(dd)); + d->MMX_L(1) = __builtin_isnan(dd) ? INT32_MIN : (int32_t)dd; +#else d->MMX_L(0) = x86_float64_to_int32_round_to_zero(s->ZMM_D(0), &env->sse_status); d->MMX_L(1) = x86_float64_to_int32_round_to_zero(s->ZMM_D(1), &env->sse_status); +#endif } int32_t helper_cvttss2si(CPUX86State *env, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float f; + memcpy(&f, &s->ZMM_S(0), sizeof(f)); + if (__builtin_isnan(f)) return INT32_MIN; + return (int32_t)f; +#else return x86_float32_to_int32_round_to_zero(s->ZMM_S(0), &env->sse_status); +#endif } int32_t helper_cvttsd2si(CPUX86State *env, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + double dd; + memcpy(&dd, &s->ZMM_D(0), sizeof(dd)); + if (__builtin_isnan(dd)) return INT32_MIN; + return (int32_t)dd; +#else return x86_float64_to_int32_round_to_zero(s->ZMM_D(0), &env->sse_status); +#endif } #ifdef TARGET_X86_64 @@ -840,6 +1388,15 @@ int64_t helper_cvttsd2sq(CPUX86State *env, ZMMReg *s) #endif #endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_rsqrtps, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) +{ + float32x4_t vf = vld1q_f32((const float *)&s->ZMM_S(0)); + float32x4_t est = vrsqrteq_f32(vf); + est = vmulq_f32(est, vrsqrtsq_f32(vf, vmulq_f32(est, est))); + vst1q_f32((float *)&d->ZMM_S(0), est); +} +#else void glue(helper_rsqrtps, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) { int old_flags = get_float_exception_flags(&env->sse_status); @@ -851,22 +1408,39 @@ void glue(helper_rsqrtps, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) } set_float_exception_flags(old_flags, &env->sse_status); } +#endif #if SHIFT == 1 void helper_rsqrtss(CPUX86State *env, ZMMReg *d, ZMMReg *v, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float32x2_t vf = vld1_dup_f32((const float *)&s->ZMM_S(0)); + float32x2_t est = vrsqrte_f32(vf); + est = vmul_f32(est, vrsqrts_f32(vf, vmul_f32(est, est))); + vst1_lane_f32((float *)&d->ZMM_S(0), est, 0); +#else int old_flags = get_float_exception_flags(&env->sse_status); - int i; d->ZMM_S(0) = float32_div(float32_one, float32_sqrt(s->ZMM_S(0), &env->sse_status), &env->sse_status); set_float_exception_flags(old_flags, &env->sse_status); +#endif + int i; for (i = 1; i < 2 << SHIFT; i++) { d->ZMM_L(i) = v->ZMM_L(i); } } #endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +void glue(helper_rcpps, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) +{ + float32x4_t vf = vld1q_f32((const float *)&s->ZMM_S(0)); + float32x4_t est = vrecpeq_f32(vf); + est = vmulq_f32(est, vrecpsq_f32(vf, est)); + vst1q_f32((float *)&d->ZMM_S(0), est); +} +#else void glue(helper_rcpps, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) { int old_flags = get_float_exception_flags(&env->sse_status); @@ -876,17 +1450,25 @@ void glue(helper_rcpps, SUFFIX)(CPUX86State *env, ZMMReg *d, ZMMReg *s) } set_float_exception_flags(old_flags, &env->sse_status); } +#endif #if SHIFT == 1 void helper_rcpss(CPUX86State *env, ZMMReg *d, ZMMReg *v, ZMMReg *s) { +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + float32x2_t vf = vld1_dup_f32((const float *)&s->ZMM_S(0)); + float32x2_t est = vrecpe_f32(vf); + est = vmul_f32(est, vrecps_f32(vf, est)); + vst1_lane_f32((float *)&d->ZMM_S(0), est, 0); +#else int old_flags = get_float_exception_flags(&env->sse_status); - int i; d->ZMM_S(0) = float32_div(float32_one, s->ZMM_S(0), &env->sse_status); + set_float_exception_flags(old_flags, &env->sse_status); +#endif + int i; for (i = 1; i < 2 << SHIFT; i++) { d->ZMM_L(i) = v->ZMM_L(i); } - set_float_exception_flags(old_flags, &env->sse_status); } #endif @@ -936,6 +1518,68 @@ void helper_insertq_i(CPUX86State *env, ZMMReg *d, ZMMReg *s, int index, int len } #endif +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 + +void glue(helper_haddps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t vv = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + float32x4_t r = vpaddq_f32(vv, vs); + vst1q_f32((float *)&d->ZMM_S(0), r); +} + +void glue(helper_hsubps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t vv = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vs = vld1q_f32((const float *)&s->ZMM_S(0)); + /* Deinterleave into even/odd pairs, then subtract */ + float32x4_t a = vuzp1q_f32(vv, vs); + float32x4_t b = vuzp2q_f32(vv, vs); + vst1q_f32((float *)&d->ZMM_S(0), vsubq_f32(a, b)); +} + +void glue(helper_haddpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t vv = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + float64x2_t r = vpaddq_f64(vv, vs); + vst1q_f64((double *)&d->ZMM_D(0), r); +} + +void glue(helper_hsubpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t vv = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vs = vld1q_f64((const double *)&s->ZMM_D(0)); + float64x2_t a = vuzp1q_f64(vv, vs); + float64x2_t b = vuzp2q_f64(vv, vs); + vst1q_f64((double *)&d->ZMM_D(0), vsubq_f64(a, b)); +} + +void glue(helper_addsubps, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); + float32x4_t vb = vld1q_f32((const float *)&s->ZMM_S(0)); + float32x4_t add = vaddq_f32(va, vb); + float32x4_t sub = vsubq_f32(va, vb); + /* Even lanes = sub, odd lanes = add: {sub[0], add[1], sub[2], add[3]} */ + static const uint32_t mask_data[4] = { 0, 0xFFFFFFFF, 0, 0xFFFFFFFF }; + uint32x4_t mask = vld1q_u32(mask_data); + vst1q_f32((float *)&d->ZMM_S(0), vbslq_f32(mask, add, sub)); +} + +void glue(helper_addsubpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) +{ + float64x2_t va = vld1q_f64((const double *)&v->ZMM_D(0)); + float64x2_t vb = vld1q_f64((const double *)&s->ZMM_D(0)); + float64x2_t add = vaddq_f64(va, vb); + float64x2_t sub = vsubq_f64(va, vb); + static const uint64_t mask_data[2] = { 0, 0xFFFFFFFFFFFFFFFFULL }; + uint64x2_t mask = vld1q_u64(mask_data); + vst1q_f64((double *)&d->ZMM_D(0), vbslq_f64(mask, add, sub)); +} + +#else + #define SSE_HELPER_HPS(name, F) \ void glue(helper_ ## name, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) \ { \ @@ -996,6 +1640,130 @@ void glue(helper_addsubpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) } } +#endif /* XBOX && __aarch64__ && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 */ + +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 +/* + * NEON-vectorized packed comparisons. Processes all 4 (PS) or 2 (PD) lanes + * in a single vector operation instead of per-element scalar loops. + */ + +static inline uint32x4_t neon_cmp_ord_ps(float32x4_t a, float32x4_t b) +{ + return vandq_u32(vceqq_f32(a, a), vceqq_f32(b, b)); +} + +static inline uint64x2_t neon_cmp_ord_pd(float64x2_t a, float64x2_t b) +{ + return vandq_u64(vceqq_f64(a, a), vceqq_f64(b, b)); +} + +static inline uint64x2_t neon_notq_u64(uint64x2_t v) +{ + return vreinterpretq_u64_u32(vmvnq_u32(vreinterpretq_u32_u64(v))); +} + +#define NEON_CMP_PS(name, expr) \ + void glue(helper_ ## name ## ps, SUFFIX)(CPUX86State *env, \ + Reg *d, Reg *v, Reg *s) \ + { \ + float32x4_t va = vld1q_f32((const float *)&v->ZMM_S(0)); \ + float32x4_t vb = vld1q_f32((const float *)&s->ZMM_S(0)); \ + uint32x4_t r = (expr); \ + vst1q_u32((uint32_t *)&d->ZMM_L(0), r); \ + } + +#define NEON_CMP_PD(name, expr64) \ + void glue(helper_ ## name ## pd, SUFFIX)(CPUX86State *env, \ + Reg *d, Reg *v, Reg *s) \ + { \ + float64x2_t da = vld1q_f64((const double *)&v->ZMM_D(0)); \ + float64x2_t db = vld1q_f64((const double *)&s->ZMM_D(0)); \ + uint64x2_t r = (expr64); \ + vst1q_u64((uint64_t *)&d->ZMM_Q(0), r); \ + } + +/* SSE base predicates (0-7) */ +NEON_CMP_PS(cmpeq, vceqq_f32(va, vb)) +NEON_CMP_PS(cmplt, vcltq_f32(va, vb)) +NEON_CMP_PS(cmple, vcleq_f32(va, vb)) +NEON_CMP_PS(cmpunord, vmvnq_u32(neon_cmp_ord_ps(va, vb))) +NEON_CMP_PS(cmpneq, vmvnq_u32(vceqq_f32(va, vb))) +NEON_CMP_PS(cmpnlt, vmvnq_u32(vcltq_f32(va, vb))) +NEON_CMP_PS(cmpnle, vmvnq_u32(vcleq_f32(va, vb))) +NEON_CMP_PS(cmpord, neon_cmp_ord_ps(va, vb)) + +NEON_CMP_PD(cmpeq, vceqq_f64(da, db)) +NEON_CMP_PD(cmplt, vcltq_f64(da, db)) +NEON_CMP_PD(cmple, vcleq_f64(da, db)) +NEON_CMP_PD(cmpunord, neon_notq_u64(neon_cmp_ord_pd(da, db))) +NEON_CMP_PD(cmpneq, neon_notq_u64(vceqq_f64(da, db))) +NEON_CMP_PD(cmpnlt, neon_notq_u64(vcltq_f64(da, db))) +NEON_CMP_PD(cmpnle, neon_notq_u64(vcleq_f64(da, db))) +NEON_CMP_PD(cmpord, neon_cmp_ord_pd(da, db)) + +/* AVX extended predicates */ +NEON_CMP_PS(cmpequ, vorrq_u32(vceqq_f32(va, vb), vmvnq_u32(neon_cmp_ord_ps(va, vb)))) +NEON_CMP_PS(cmpnge, vmvnq_u32(vcgeq_f32(va, vb))) +NEON_CMP_PS(cmpngt, vmvnq_u32(vcgtq_f32(va, vb))) +NEON_CMP_PS(cmpfalse, vdupq_n_u32(0)) +NEON_CMP_PS(cmpnequ, vmvnq_u32(vorrq_u32(vceqq_f32(va, vb), vmvnq_u32(neon_cmp_ord_ps(va, vb))))) +NEON_CMP_PS(cmpge, vcgeq_f32(va, vb)) +NEON_CMP_PS(cmpgt, vcgtq_f32(va, vb)) +NEON_CMP_PS(cmptrue, vdupq_n_u32(0xFFFFFFFF)) + +NEON_CMP_PD(cmpequ, vorrq_u64(vceqq_f64(da, db), neon_notq_u64(neon_cmp_ord_pd(da, db)))) +NEON_CMP_PD(cmpnge, neon_notq_u64(vcgeq_f64(da, db))) +NEON_CMP_PD(cmpngt, neon_notq_u64(vcgtq_f64(da, db))) +NEON_CMP_PD(cmpfalse, vdupq_n_u64(0)) +NEON_CMP_PD(cmpnequ, neon_notq_u64(vorrq_u64(vceqq_f64(da, db), neon_notq_u64(neon_cmp_ord_pd(da, db))))) +NEON_CMP_PD(cmpge, vcgeq_f64(da, db)) +NEON_CMP_PD(cmpgt, vcgtq_f64(da, db)) +NEON_CMP_PD(cmptrue, vdupq_n_u64(UINT64_MAX)) + +/* "s"/"q" signaling variants use same NEON ops (no exception tracking) */ +NEON_CMP_PS(cmpeqs, vceqq_f32(va, vb)) +NEON_CMP_PS(cmpltq, vcltq_f32(va, vb)) +NEON_CMP_PS(cmpleq, vcleq_f32(va, vb)) +NEON_CMP_PS(cmpunords, vmvnq_u32(neon_cmp_ord_ps(va, vb))) +NEON_CMP_PS(cmpneqq, vmvnq_u32(vceqq_f32(va, vb))) +NEON_CMP_PS(cmpnltq, vmvnq_u32(vcltq_f32(va, vb))) +NEON_CMP_PS(cmpnleq, vmvnq_u32(vcleq_f32(va, vb))) +NEON_CMP_PS(cmpords, neon_cmp_ord_ps(va, vb)) +NEON_CMP_PS(cmpequs, vorrq_u32(vceqq_f32(va, vb), vmvnq_u32(neon_cmp_ord_ps(va, vb)))) +NEON_CMP_PS(cmpngeq, vmvnq_u32(vcgeq_f32(va, vb))) +NEON_CMP_PS(cmpngtq, vmvnq_u32(vcgtq_f32(va, vb))) +NEON_CMP_PS(cmpfalses, vdupq_n_u32(0)) +NEON_CMP_PS(cmpnequs, vmvnq_u32(vorrq_u32(vceqq_f32(va, vb), vmvnq_u32(neon_cmp_ord_ps(va, vb))))) +NEON_CMP_PS(cmpgeq, vcgeq_f32(va, vb)) +NEON_CMP_PS(cmpgtq, vcgtq_f32(va, vb)) +NEON_CMP_PS(cmptrues, vdupq_n_u32(0xFFFFFFFF)) + +NEON_CMP_PD(cmpeqs, vceqq_f64(da, db)) +NEON_CMP_PD(cmpltq, vcltq_f64(da, db)) +NEON_CMP_PD(cmpleq, vcleq_f64(da, db)) +NEON_CMP_PD(cmpunords, neon_notq_u64(neon_cmp_ord_pd(da, db))) +NEON_CMP_PD(cmpneqq, neon_notq_u64(vceqq_f64(da, db))) +NEON_CMP_PD(cmpnltq, neon_notq_u64(vcltq_f64(da, db))) +NEON_CMP_PD(cmpnleq, neon_notq_u64(vcleq_f64(da, db))) +NEON_CMP_PD(cmpords, neon_cmp_ord_pd(da, db)) +NEON_CMP_PD(cmpequs, vorrq_u64(vceqq_f64(da, db), neon_notq_u64(neon_cmp_ord_pd(da, db)))) +NEON_CMP_PD(cmpngeq, neon_notq_u64(vcgeq_f64(da, db))) +NEON_CMP_PD(cmpngtq, neon_notq_u64(vcgtq_f64(da, db))) +NEON_CMP_PD(cmpfalses, vdupq_n_u64(0)) +NEON_CMP_PD(cmpnequs, neon_notq_u64(vorrq_u64(vceqq_f64(da, db), neon_notq_u64(neon_cmp_ord_pd(da, db))))) +NEON_CMP_PD(cmpgeq, vcgeq_f64(da, db)) +NEON_CMP_PD(cmpgtq, vcgtq_f64(da, db)) +NEON_CMP_PD(cmptrues, vdupq_n_u64(UINT64_MAX)) + +#undef NEON_CMP_PS +#undef NEON_CMP_PD + +#define SSE_HELPER_CMP_P(name, F, C) + +#endif /* XBOX && __aarch64__ && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1 -- NEON CMP */ + +#if !(defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT && SHIFT == 1) #define SSE_HELPER_CMP_P(name, F, C) \ void glue(helper_ ## name ## ps, SUFFIX)(CPUX86State *env, \ Reg *d, Reg *v, Reg *s) \ @@ -1014,6 +1782,7 @@ void glue(helper_addsubpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *v, Reg *s) d->ZMM_Q(i) = C(F(64, v->ZMM_D(i), s->ZMM_D(i))) ? -1 : 0; \ } \ } +#endif /* !NEON CMP */ #if SHIFT == 1 #define SSE_HELPER_CMP(name, F, C) \ @@ -1052,10 +1821,15 @@ static inline bool FPU_GE(FloatRelation x) /* We must make sure we evaluate the argument in case it is a signalling NAN */ #define FPU_FALSE(x) (x == float_relation_equal && 0) +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT +#define FPU_CMPQ(size, a, b) float ## size ## _compare_native(a, b) +#define FPU_CMPS(size, a, b) float ## size ## _compare_native(a, b) +#else #define FPU_CMPQ(size, a, b) \ float ## size ## _compare_quiet(a, b, &env->sse_status) #define FPU_CMPS(size, a, b) \ float ## size ## _compare(a, b, &env->sse_status) +#endif #else #define SSE_HELPER_CMP(name, F, C) SSE_HELPER_CMP_P(name, F, C) @@ -1109,7 +1883,11 @@ void helper_ucomiss(CPUX86State *env, Reg *d, Reg *s) s0 = d->ZMM_S(0); s1 = s->ZMM_S(0); +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + ret = float32_compare_native(s0, s1); +#else ret = float32_compare_quiet(s0, s1, &env->sse_status); +#endif CC_SRC = comis_eflags[ret + 1]; CC_OP = CC_OP_EFLAGS; } @@ -1121,7 +1899,11 @@ void helper_comiss(CPUX86State *env, Reg *d, Reg *s) s0 = d->ZMM_S(0); s1 = s->ZMM_S(0); +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + ret = float32_compare_native(s0, s1); +#else ret = float32_compare(s0, s1, &env->sse_status); +#endif CC_SRC = comis_eflags[ret + 1]; CC_OP = CC_OP_EFLAGS; } @@ -1133,7 +1915,11 @@ void helper_ucomisd(CPUX86State *env, Reg *d, Reg *s) d0 = d->ZMM_D(0); d1 = s->ZMM_D(0); +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + ret = float64_compare_native(d0, d1); +#else ret = float64_compare_quiet(d0, d1, &env->sse_status); +#endif CC_SRC = comis_eflags[ret + 1]; CC_OP = CC_OP_EFLAGS; } @@ -1145,7 +1931,11 @@ void helper_comisd(CPUX86State *env, Reg *d, Reg *s) d0 = d->ZMM_D(0); d1 = s->ZMM_D(0); +#if defined(XBOX) && defined(__aarch64__) && XEMU_OPT_NATIVE_FLOAT + ret = float64_compare_native(d0, d1); +#else ret = float64_compare(d0, d1, &env->sse_status); +#endif CC_SRC = comis_eflags[ret + 1]; CC_OP = CC_OP_EFLAGS; } diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 540020c82b..8922e0cc61 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -546,6 +546,9 @@ static inline void gen_unary_fp_sse(DisasContext *s, X86DecodedInsn *decode, return; } fn(tcg_env, OP_PTR0, OP_PTR1, OP_PTR2); +#if defined(XBOX) + g_fpu_profile.sse_arith_scalar++; +#endif } else { SSEFunc_0_epp ps, pd, fn; ps = s->vex_l ? ps_ymm : ps_xmm; @@ -556,6 +559,9 @@ static inline void gen_unary_fp_sse(DisasContext *s, X86DecodedInsn *decode, return; } fn(tcg_env, OP_PTR0, OP_PTR2); +#if defined(XBOX) + g_fpu_profile.sse_arith_packed++; +#endif } } #define UNARY_FP_SSE(uname, lname) \ @@ -585,10 +591,16 @@ static inline void gen_fp_sse(DisasContext *s, X86DecodedInsn *decode, SSEFunc_0_eppp ps, pd, fn; if ((s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) != 0) { fn = s->prefix & PREFIX_REPZ ? ss : sd; +#if defined(XBOX) + g_fpu_profile.sse_arith_scalar++; +#endif } else { ps = s->vex_l ? ps_ymm : ps_xmm; pd = s->vex_l ? pd_ymm : pd_xmm; fn = s->prefix & PREFIX_DATA ? pd : ps; +#if defined(XBOX) + g_fpu_profile.sse_arith_packed++; +#endif } if (fn) { fn(tcg_env, OP_PTR0, OP_PTR1, OP_PTR2); @@ -4268,6 +4280,9 @@ static void gen_VCMP(DisasContext *s, X86DecodedInsn *decode) !!(s->prefix & PREFIX_DATA) /* pd */ + (s->vex_l << 2); gen_helper_cmp_funcs[index][b](tcg_env, OP_PTR0, OP_PTR1, OP_PTR2); +#if defined(XBOX) + g_fpu_profile.sse_cmp++; +#endif } static void gen_VCOMI(DisasContext *s, X86DecodedInsn *decode) @@ -4276,6 +4291,9 @@ static void gen_VCOMI(DisasContext *s, X86DecodedInsn *decode) fn = s->prefix & PREFIX_DATA ? gen_helper_comisd : gen_helper_comiss; fn(tcg_env, OP_PTR1, OP_PTR2); assume_cc_op(s, CC_OP_EFLAGS); +#if defined(XBOX) + g_fpu_profile.sse_cmp++; +#endif } static void gen_VCVTPD2PS(DisasContext *s, X86DecodedInsn *decode) @@ -4285,6 +4303,9 @@ static void gen_VCVTPD2PS(DisasContext *s, X86DecodedInsn *decode) } else { gen_helper_cvtpd2ps_xmm(tcg_env, OP_PTR0, OP_PTR2); } +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static void gen_VCVTPS2PD(DisasContext *s, X86DecodedInsn *decode) @@ -4294,6 +4315,9 @@ static void gen_VCVTPS2PD(DisasContext *s, X86DecodedInsn *decode) } else { gen_helper_cvtps2pd_xmm(tcg_env, OP_PTR0, OP_PTR2); } +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static void gen_VCVTPS2PH(DisasContext *s, X86DecodedInsn *decode) @@ -4313,11 +4337,17 @@ static void gen_VCVTPS2PH(DisasContext *s, X86DecodedInsn *decode) static void gen_VCVTSD2SS(DisasContext *s, X86DecodedInsn *decode) { gen_helper_cvtsd2ss(tcg_env, OP_PTR0, OP_PTR1, OP_PTR2); +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static void gen_VCVTSS2SD(DisasContext *s, X86DecodedInsn *decode) { gen_helper_cvtss2sd(tcg_env, OP_PTR0, OP_PTR1, OP_PTR2); +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static void gen_VCVTSI2Sx(DisasContext *s, X86DecodedInsn *decode) @@ -4348,6 +4378,9 @@ static void gen_VCVTSI2Sx(DisasContext *s, X86DecodedInsn *decode) } else { gen_helper_cvtsi2ss(tcg_env, OP_PTR0, in); } +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static inline void gen_VCVTtSx2SI(DisasContext *s, X86DecodedInsn *decode, @@ -4393,6 +4426,9 @@ static void gen_VCVTSx2SI(DisasContext *s, X86DecodedInsn *decode) gen_VCVTtSx2SI(s, decode, gen_helper_cvtss2si, gen_helper_cvtss2sq, gen_helper_cvtsd2si, gen_helper_cvtsd2sq); +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static void gen_VCVTTSx2SI(DisasContext *s, X86DecodedInsn *decode) @@ -4400,6 +4436,9 @@ static void gen_VCVTTSx2SI(DisasContext *s, X86DecodedInsn *decode) gen_VCVTtSx2SI(s, decode, gen_helper_cvttss2si, gen_helper_cvttss2sq, gen_helper_cvttsd2si, gen_helper_cvttsd2sq); +#if defined(XBOX) + g_fpu_profile.sse_cvt++; +#endif } static void gen_VEXTRACTx128(DisasContext *s, X86DecodedInsn *decode) @@ -4671,6 +4710,9 @@ static void gen_VUCOMI(DisasContext *s, X86DecodedInsn *decode) fn = s->prefix & PREFIX_DATA ? gen_helper_ucomisd : gen_helper_ucomiss; fn(tcg_env, OP_PTR1, OP_PTR2); assume_cc_op(s, CC_OP_EFLAGS); +#if defined(XBOX) + g_fpu_profile.sse_cmp++; +#endif } static void gen_VZEROALL(DisasContext *s, X86DecodedInsn *decode) diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index d7377a92ea..9f8a59f87b 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -30,10 +30,21 @@ #include "access.h" /* float macros */ +#if defined(USE_HARD_FPU) && defined(__aarch64__) +#define USE_NATIVE_DOUBLE_STORAGE 1 +#define FT0 (env->ft0_native) +#define ST0 (env->fpregs[env->fpstt].native_d) +#define ST(n) (env->fpregs[(env->fpstt + (n)) & 7].native_d) +#define ST1 ST(1) +#define FPREG_VAL(fpreg) ((fpreg).native_d) +#else +#define USE_NATIVE_DOUBLE_STORAGE 0 #define FT0 (env->ft0) #define ST0 (env->fpregs[env->fpstt].d) #define ST(n) (env->fpregs[(env->fpstt + (n)) & 7].d) #define ST1 ST(1) +#define FPREG_VAL(fpreg) ((fpreg).d) +#endif #define FPU_RC_SHIFT 10 #define FPU_RC_MASK (3 << FPU_RC_SHIFT) @@ -73,6 +84,277 @@ #define floatx80_ln2_d make_floatx80(0x3ffe, 0xb17217f7d1cf79abLL) #define floatx80_pi_d make_floatx80(0x4000, 0xc90fdaa22168c234LL) +/* + * ARM64 FPU: convert floatx80 <-> double via inline bit manipulation, + * then use the native ARM64 double-precision FPU for arithmetic. + * + * Two acceleration mechanisms exist on ARM64: + * + * 1) fp_jit (compile-time): fpu_helper.c is compiled twice via + * fpu_helper_hard.c producing helper_*__soft and helper_*__hard symbols. + * The __hard helpers always use native double (no runtime branch). + * Selection is made once at TCG translation time via g_use_fp_jit. + * + * 2) fp_safe (runtime): When fp_jit is off, the __soft helpers use + * floatx80_*_rt wrappers that check g_xemu_fp_safe on every call. + * Can be toggled at runtime via xemu_set_fp_safe(). + */ +#if defined(XBOX) && defined(__aarch64__) + +#include + +#ifndef USE_HARD_FPU +#endif + +static inline double fx80_to_f64(floatx80 a) +{ + uint64_t mant = a.low; + uint16_t exp_sign = a.high; + int sign = (exp_sign >> 15) & 1; + int exp = exp_sign & 0x7FFF; + + if (exp == 0x7FFF) { + uint64_t d_bits = ((uint64_t)sign << 63) | (UINT64_C(0x7FF) << 52); + if (mant & UINT64_C(0x7FFFFFFFFFFFFFFF)) { + d_bits |= UINT64_C(0x8000000000000); + } + double d; + memcpy(&d, &d_bits, sizeof(d)); + return d; + } + + if (exp == 0 || !(mant & UINT64_C(0x8000000000000000))) { + return sign ? -0.0 : 0.0; + } + + int d_exp = exp - 16383 + 1023; + if (d_exp >= 0x7FF) { + return sign ? -INFINITY : INFINITY; + } + if (d_exp <= 0) { + return sign ? -0.0 : 0.0; + } + + uint64_t d_mant = (mant & UINT64_C(0x7FFFFFFFFFFFFFFF)) >> 11; + uint64_t d_bits = ((uint64_t)sign << 63) | ((uint64_t)d_exp << 52) | d_mant; + double d; + memcpy(&d, &d_bits, sizeof(d)); + return d; +} + +static inline floatx80 f64_to_fx80(double d) +{ + uint64_t d_bits; + memcpy(&d_bits, &d, sizeof(d_bits)); + int sign = (d_bits >> 63) & 1; + int d_exp = (d_bits >> 52) & 0x7FF; + uint64_t d_mant = d_bits & UINT64_C(0xFFFFFFFFFFFFF); + + if (d_exp == 0x7FF) { + uint64_t low = UINT64_C(0x8000000000000000); + if (d_mant) { + low |= (d_mant << 11) | UINT64_C(0x4000000000000000); + } + return make_floatx80((sign << 15) | 0x7FFF, low); + } + + if (d_exp == 0) { + return make_floatx80((uint16_t)(sign << 15), 0); + } + + int x_exp = d_exp - 1023 + 16383; + uint64_t low = UINT64_C(0x8000000000000000) | (d_mant << 11); + return make_floatx80((sign << 15) | x_exp, low); +} + +static inline floatx80 pack_arm64(floatx80 v, float_status *status) +{ + switch (status->floatx80_rounding_precision) { + case floatx80_precision_s: { + float f = (float)fx80_to_f64(v); + return f64_to_fx80((double)f); + } + case floatx80_precision_d: + default: + return v; + } +} + +#ifndef USE_HARD_FPU +#endif /* !USE_HARD_FPU */ + +#ifdef USE_HARD_FPU +/* + * Native double storage: ST0/FT0 are now `double`, so all floatx80_* + * operations become native double operations and conversions are trivial. + */ + +static inline FloatRelation floatx80_compare_nds(double a, double b, float_status *s) +{ + (void)s; + if (a < b) return float_relation_less; + if (a > b) return float_relation_greater; + if (a == b) return float_relation_equal; + return float_relation_unordered; +} + +static inline float32 floatx80_to_float32_nds(double a, float_status *s) +{ + (void)s; + union { float f; float32 i; } u; + u.f = (float)a; + return u.i; +} + +static inline double float32_to_floatx80_nds(float32 val, float_status *s) +{ + (void)s; + union { float32 i; float f; } u; + u.i = val; + return (double)u.f; +} + +static inline double float64_to_floatx80_nds(float64 val, float_status *s) +{ + (void)s; + union { float64 i; double d; } u; + u.i = val; + return u.d; +} + +static inline float64 floatx80_to_float64_nds(double a, float_status *s) +{ + (void)s; + union { double d; float64 i; } u; + u.d = a; + return u.i; +} + +#define floatx80_add(a, b, s) ((void)(s), (a) + (b)) +#define floatx80_sub(a, b, s) ((void)(s), (a) - (b)) +#define floatx80_mul(a, b, s) ((void)(s), (a) * (b)) +#define floatx80_div(a, b, s) ((void)(s), (a) / (b)) +#define floatx80_compare floatx80_compare_nds +#define floatx80_compare_quiet floatx80_compare_nds +#define float32_to_floatx80 float32_to_floatx80_nds +#define floatx80_to_float32 floatx80_to_float32_nds +#define float64_to_floatx80 float64_to_floatx80_nds +#define floatx80_to_float64 floatx80_to_float64_nds +#define int32_to_floatx80(a, s) ((void)(s), (double)(a)) +#define int64_to_floatx80(a, s) ((void)(s), (double)(a)) +#define floatx80_to_int32(a, s) ((void)(s), (int32_t)(a)) +#define floatx80_to_int64(a, s) ((void)(s), (int64_t)(a)) +#define floatx80_to_int32_round_to_zero(a, s) ((void)(s), (int32_t)(a)) +#define floatx80_to_int64_round_to_zero(a, s) ((void)(s), (int64_t)(a)) + +#define floatx80_is_neg(a) signbit(a) +#define floatx80_is_zero(a) ((a) == 0.0) +#define floatx80_is_zero_or_denormal(a) ((a) == 0.0) +#define floatx80_is_any_nan(a) isnan(a) +#define floatx80_is_infinity(a, ...) isinf(a) +#define floatx80_is_signaling_nan(a, s) 0 +#define floatx80_silence_nan(a, s) (a) +#define floatx80_invalid_encoding(a, ...) 0 + +#define floatx80_chs(a) (-(a)) +#define floatx80_abs(a) fabs(a) +#define floatx80_sqrt(a, s) ((void)(s), sqrt(a)) + +#define floatx80_round(a, s) ((void)(s), rint(a)) +#define floatx80_round_to_int(a, s) ((void)(s), rint(a)) + +#undef floatx80_zero +#undef floatx80_one +#undef floatx80_half +#undef floatx80_lg2 +#undef floatx80_lg2_d +#undef floatx80_l2e +#undef floatx80_l2e_d +#undef floatx80_l2t +#undef floatx80_l2t_u +#undef floatx80_ln2 +#undef floatx80_ln2_d +#undef floatx80_pi +#undef floatx80_pi_d + +#define floatx80_zero 0.0 +#define floatx80_one 1.0 +#define floatx80_pi M_PI +#define floatx80_pi_d M_PI +#define floatx80_half 0.5 +#define floatx80_ln2 M_LN2 +#define floatx80_ln2_d M_LN2 +#define floatx80_l2e M_LOG2E +#define floatx80_l2e_d M_LOG2E +#define floatx80_l2t 3.32192809488736234787 +#define floatx80_l2t_u 3.32192809488736234787 +#define floatx80_lg2 (M_LN2/M_LN10) +#define floatx80_lg2_d (M_LN2/M_LN10) + +#define floatx80_lt(a, b, s) ((void)(s), (a) < (b)) +#define floatx80_le(a, b, s) ((void)(s), (a) <= (b)) +#define floatx80_eq(a, b, s) ((void)(s), (a) == (b)) + +#define make_floatx80(hi, lo) fx80_to_f64((floatx80){.low=(lo), .high=(hi)}) + +#define floatx80_to_double(env, a) (a) +#define double_to_floatx80(env, a) (a) + +#define floatx80_default_nan(s) ((void)(s), NAN) +#define floatx80_default_inf(sign, s) ((void)(s), (sign) ? -INFINITY : INFINITY) + +static inline double floatx80_scalbn_nds(double a, int n, float_status *s) +{ + (void)s; + return scalbn(a, n); +} +#define floatx80_scalbn floatx80_scalbn_nds + +static inline double floatx80_modrem_nds(double a, double b, bool mod, + uint64_t *quotient, float_status *s) +{ + (void)s; + double q = (mod) ? fmod(a, b) : remainder(a, b); + if (quotient) { + double full_q = trunc(a / b); + *quotient = (uint64_t)(int64_t)full_q; + } + return q; +} +#define floatx80_modrem floatx80_modrem_nds + +#define floatx80_mod(a, b, s) ((void)(s), fmod((a), (b))) + +#else /* !USE_HARD_FPU: pure softfloat (no runtime dispatch) */ + +#endif /* USE_HARD_FPU */ + +#endif /* XBOX && __aarch64__ */ + +#if defined(XBOX) && !defined(USE_HARD_FPU) +void xemu_set_fp_safe(bool enable) +{ + (void)enable; +} + +bool xemu_get_fp_safe(void) +{ + return false; +} +#endif /* XBOX && !USE_HARD_FPU */ + +/* + * FP JIT: dual-compilation via fpu_helper_hard.c. + * + * fpu_helper.c is compiled twice on x86_64 and aarch64: + * 1) Normally (without USE_HARD_FPU) -> produces helper_*__soft symbols + * 2) Via fpu_helper_hard.c (with USE_HARD_FPU) -> produces helper_*__hard symbols + * + * On x86_64, __hard helpers use native long double (80-bit x87). + * On aarch64, __hard helpers use native double via fx80_to_f64/f64_to_fx80. + * + * The MAP_HELPER_SOFT_HARD macro renames helpers to the appropriate variant. + */ #if defined(XBOX) && defined(__x86_64__) #ifdef USE_HARD_FPU /* @@ -176,11 +458,16 @@ floatx80 int32_to_floatx80__hard(int32_t a, float_status *status) #define floatx80_to_float64 floatx80_to_float64__hard #define int32_to_floatx80 int32_to_floatx80__hard #endif /* USE_HARD_FPU */ +#endif /* XBOX && __x86_64__ */ +#if defined(XBOX) && (defined(__x86_64__) || defined(__aarch64__)) #ifdef USE_HARD_FPU #define MAP_HELPER_SOFT_HARD(func) helper_ ## func ## __hard +extern int g_fpu_helper_calls; +#define FPU_HELPER_COUNT() (g_fpu_helper_calls++) #else #define MAP_HELPER_SOFT_HARD(func) helper_ ## func ## __soft +#define FPU_HELPER_COUNT() ((void)0) #endif #define helper_flds_FT0 MAP_HELPER_SOFT_HARD(flds_FT0) @@ -264,7 +551,7 @@ floatx80 int32_to_floatx80__hard(int32_t a, float_status *status) #define helper_fsave MAP_HELPER_SOFT_HARD(fsave) #define helper_frstor MAP_HELPER_SOFT_HARD(frstor) -#endif /* defined(XBOX) && defined(__x86_64__) */ +#endif /* defined(XBOX) && (__x86_64__ || __aarch64__) */ static inline void fpush(CPUX86State *env) { @@ -298,6 +585,7 @@ static void do_fstt(X86Access *ac, target_ulong ptr, floatx80 f) /* x87 FPU helpers */ +#if !USE_NATIVE_DOUBLE_STORAGE static inline double floatx80_to_double(CPUX86State *env, floatx80 a) { union { @@ -319,6 +607,7 @@ static inline floatx80 double_to_floatx80(CPUX86State *env, double a) u.d = a; return float64_to_floatx80(u.f64, &env->fp_status); } +#endif #ifndef USE_HARD_FPU static void fpu_set_exception(CPUX86State *env, int mask) @@ -415,6 +704,15 @@ static void merge_exception_flags(CPUX86State *env, int old_flags) #endif } +#if USE_NATIVE_DOUBLE_STORAGE +static inline double helper_fdiv(CPUX86State *env, double a, double b) +{ + int old_flags = save_exception_flags(env); + double ret = a / b; + merge_exception_flags(env, old_flags); + return ret; +} +#else static inline floatx80 helper_fdiv(CPUX86State *env, floatx80 a, floatx80 b) { int old_flags = save_exception_flags(env); @@ -422,6 +720,7 @@ static inline floatx80 helper_fdiv(CPUX86State *env, floatx80 a, floatx80 b) merge_exception_flags(env, old_flags); return ret; } +#endif static void fpu_raise_exception(CPUX86State *env, uintptr_t retaddr) { @@ -477,7 +776,7 @@ void helper_flds_ST0(CPUX86State *env, uint32_t val) new_fpstt = (env->fpstt - 1) & 7; u.i = val; - env->fpregs[new_fpstt].d = float32_to_floatx80(u.f, &env->fp_status); + FPREG_VAL(env->fpregs[new_fpstt]) = float32_to_floatx80(u.f, &env->fp_status); env->fpstt = new_fpstt; env->fptags[new_fpstt] = 0; /* validate stack entry */ merge_exception_flags(env, old_flags); @@ -494,7 +793,7 @@ void helper_fldl_ST0(CPUX86State *env, uint64_t val) new_fpstt = (env->fpstt - 1) & 7; u.i = val; - env->fpregs[new_fpstt].d = float64_to_floatx80(u.f, &env->fp_status); + FPREG_VAL(env->fpregs[new_fpstt]) = float64_to_floatx80(u.f, &env->fp_status); env->fpstt = new_fpstt; env->fptags[new_fpstt] = 0; /* validate stack entry */ merge_exception_flags(env, old_flags); @@ -513,7 +812,7 @@ void helper_fildl_ST0(CPUX86State *env, int32_t val) FloatX80RoundPrec old = tmp_maximise_precision(&env->fp_status); new_fpstt = (env->fpstt - 1) & 7; - env->fpregs[new_fpstt].d = int32_to_floatx80(val, &env->fp_status); + FPREG_VAL(env->fpregs[new_fpstt]) = int32_to_floatx80(val, &env->fp_status); env->fpstt = new_fpstt; env->fptags[new_fpstt] = 0; /* validate stack entry */ @@ -526,7 +825,7 @@ void helper_fildll_ST0(CPUX86State *env, int64_t val) FloatX80RoundPrec old = tmp_maximise_precision(&env->fp_status); new_fpstt = (env->fpstt - 1) & 7; - env->fpregs[new_fpstt].d = int64_to_floatx80(val, &env->fp_status); + FPREG_VAL(env->fpregs[new_fpstt]) = int64_to_floatx80(val, &env->fp_status); env->fpstt = new_fpstt; env->fptags[new_fpstt] = 0; /* validate stack entry */ @@ -647,7 +946,11 @@ void helper_fldt_ST0(CPUX86State *env, target_ulong ptr) access_prepare(&ac, env, ptr, 10, MMU_DATA_LOAD, GETPC()); new_fpstt = (env->fpstt - 1) & 7; +#if USE_NATIVE_DOUBLE_STORAGE + env->fpregs[new_fpstt].native_d = fx80_to_f64(do_fldt(&ac, ptr)); +#else env->fpregs[new_fpstt].d = do_fldt(&ac, ptr); +#endif env->fpstt = new_fpstt; env->fptags[new_fpstt] = 0; /* validate stack entry */ } @@ -657,7 +960,11 @@ void helper_fstt_ST0(CPUX86State *env, target_ulong ptr) X86Access ac; access_prepare(&ac, env, ptr, 10, MMU_DATA_STORE, GETPC()); +#if USE_NATIVE_DOUBLE_STORAGE + do_fstt(&ac, ptr, f64_to_fx80(ST0)); +#else do_fstt(&ac, ptr, ST0); +#endif } void helper_fpush(CPUX86State *env) @@ -711,9 +1018,11 @@ void helper_fmov_STN_ST0(CPUX86State *env, int st_index) void helper_fxchg_ST0_STN(CPUX86State *env, int st_index) { - floatx80 tmp; - - tmp = ST(st_index); +#if USE_NATIVE_DOUBLE_STORAGE + double tmp = ST(st_index); +#else + floatx80 tmp = ST(st_index); +#endif ST(st_index) = ST0; ST0 = tmp; } @@ -772,6 +1081,7 @@ void helper_fucomi_ST0_FT0(CPUX86State *env) void helper_fadd_ST0_FT0(CPUX86State *env) { + FPU_HELPER_COUNT(); int old_flags = save_exception_flags(env); ST0 = floatx80_add(ST0, FT0, &env->fp_status); merge_exception_flags(env, old_flags); @@ -779,6 +1089,7 @@ void helper_fadd_ST0_FT0(CPUX86State *env) void helper_fmul_ST0_FT0(CPUX86State *env) { + FPU_HELPER_COUNT(); int old_flags = save_exception_flags(env); ST0 = floatx80_mul(ST0, FT0, &env->fp_status); merge_exception_flags(env, old_flags); @@ -786,6 +1097,7 @@ void helper_fmul_ST0_FT0(CPUX86State *env) void helper_fsub_ST0_FT0(CPUX86State *env) { + FPU_HELPER_COUNT(); int old_flags = save_exception_flags(env); ST0 = floatx80_sub(ST0, FT0, &env->fp_status); merge_exception_flags(env, old_flags); @@ -793,6 +1105,7 @@ void helper_fsub_ST0_FT0(CPUX86State *env) void helper_fsubr_ST0_FT0(CPUX86State *env) { + FPU_HELPER_COUNT(); int old_flags = save_exception_flags(env); ST0 = floatx80_sub(FT0, ST0, &env->fp_status); merge_exception_flags(env, old_flags); @@ -800,11 +1113,13 @@ void helper_fsubr_ST0_FT0(CPUX86State *env) void helper_fdiv_ST0_FT0(CPUX86State *env) { + FPU_HELPER_COUNT(); ST0 = helper_fdiv(env, ST0, FT0); } void helper_fdivr_ST0_FT0(CPUX86State *env) { + FPU_HELPER_COUNT(); ST0 = helper_fdiv(env, FT0, ST0); } @@ -840,18 +1155,12 @@ void helper_fsubr_STN_ST0(CPUX86State *env, int st_index) void helper_fdiv_STN_ST0(CPUX86State *env, int st_index) { - floatx80 *p; - - p = &ST(st_index); - *p = helper_fdiv(env, *p, ST0); + ST(st_index) = helper_fdiv(env, ST(st_index), ST0); } void helper_fdivr_STN_ST0(CPUX86State *env, int st_index) { - floatx80 *p; - - p = &ST(st_index); - *p = helper_fdiv(env, ST0, *p); + ST(st_index) = helper_fdiv(env, ST0, ST(st_index)); } /* misc FPU operations */ @@ -1038,7 +1347,6 @@ void helper_fninit(CPUX86State *env) void helper_fbld_ST0(CPUX86State *env, target_ulong ptr) { X86Access ac; - floatx80 tmp; uint64_t val; unsigned int v; int i; @@ -1050,12 +1358,11 @@ void helper_fbld_ST0(CPUX86State *env, target_ulong ptr) v = access_ldb(&ac, ptr + i); val = (val * 100) + ((v >> 4) * 10) + (v & 0xf); } - tmp = int64_to_floatx80(val, &env->fp_status); - if (access_ldb(&ac, ptr + 9) & 0x80) { - tmp = floatx80_chs(tmp); - } fpush(env); - ST0 = tmp; + ST0 = int64_to_floatx80(val, &env->fp_status); + if (access_ldb(&ac, ptr + 9) & 0x80) { + ST0 = floatx80_chs(ST0); + } } void helper_fbst_ST0(CPUX86State *env, target_ulong ptr) @@ -1064,11 +1371,9 @@ void helper_fbst_ST0(CPUX86State *env, target_ulong ptr) int v; target_ulong mem_ref, mem_end; int64_t val; - CPU_LDoubleU temp; X86Access ac; access_prepare(&ac, env, ptr, 10, MMU_DATA_STORE, GETPC()); - temp.d = ST0; val = floatx80_to_int64(ST0, &env->fp_status); mem_ref = ptr; @@ -1084,7 +1389,7 @@ void helper_fbst_ST0(CPUX86State *env, target_ulong ptr) return; } mem_end = mem_ref + 9; - if (SIGND(temp)) { + if (floatx80_is_neg(ST0)) { access_stb(&ac, mem_end, 0x80); val = -val; } else { @@ -1336,6 +1641,10 @@ static const struct f2xm1_data f2xm1_table[65] = { void helper_f2xm1(CPUX86State *env) { int old_flags = save_exception_flags(env); +#if USE_NATIVE_DOUBLE_STORAGE + ST0 = exp2(ST0) - 1.0; +#else + { uint64_t sig = extractFloatx80Frac(ST0); int32_t exp = extractFloatx80Exp(ST0); bool sign = extractFloatx80Sign(ST0); @@ -1491,6 +1800,8 @@ void helper_f2xm1(CPUX86State *env) env->fp_status.floatx80_rounding_precision = save_prec; } + } +#endif merge_exception_flags(env, old_flags); } @@ -1569,6 +1880,11 @@ static const struct fpatan_data fpatan_table[9] = { void helper_fpatan(CPUX86State *env) { int old_flags = save_exception_flags(env); +#if USE_NATIVE_DOUBLE_STORAGE + ST1 = atan2(ST1, ST0); + fpop(env); +#else + { uint64_t arg0_sig = extractFloatx80Frac(ST0); int32_t arg0_exp = extractFloatx80Exp(ST0); bool arg0_sign = extractFloatx80Sign(ST0); @@ -2001,23 +2317,29 @@ void helper_fpatan(CPUX86State *env) rsig0, rsig1, &env->fp_status); } + } fpop(env); +#endif merge_exception_flags(env, old_flags); } void helper_fxtract(CPUX86State *env) { int old_flags = save_exception_flags(env); - CPU_LDoubleU temp; - - temp.d = ST0; if (floatx80_is_zero(ST0)) { - /* Easy way to generate -inf and raising division by 0 exception */ ST0 = floatx80_div(floatx80_chs(floatx80_one), floatx80_zero, &env->fp_status); fpush(env); - ST0 = temp.d; +#if USE_NATIVE_DOUBLE_STORAGE + ST0 = 0.0; +#else + { + CPU_LDoubleU temp; + temp.d = ST1; /* ST1 after push = old ST0 value captured before div */ + ST0 = temp.d; + } +#endif } else if (floatx80_invalid_encoding(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); @@ -2035,8 +2357,18 @@ void helper_fxtract(CPUX86State *env) ST0 = ST1; ST1 = floatx80_default_inf(0, &env->fp_status); } else { +#if USE_NATIVE_DOUBLE_STORAGE + int exponent; + double significand = frexp(ST0, &exponent); + exponent--; + ST0 = (double)exponent; + fpush(env); + ST0 = ldexp(significand, 1); +#else + CPU_LDoubleU temp; int expdif; + temp.d = ST0; if (EXPD(temp) == 0) { int shift = clz64(temp.l.lower); temp.l.lower <<= shift; @@ -2045,11 +2377,11 @@ void helper_fxtract(CPUX86State *env) } else { expdif = EXPD(temp) - EXPBIAS; } - /* DP exponent bias */ ST0 = int32_to_floatx80(expdif, &env->fp_status); fpush(env); BIASEXPONENT(temp); ST0 = temp.d; +#endif } merge_exception_flags(env, old_flags); } @@ -2058,6 +2390,34 @@ static void helper_fprem_common(CPUX86State *env, bool mod) { int old_flags = save_exception_flags(env); uint64_t quotient; + +#if USE_NATIVE_DOUBLE_STORAGE + int exp0, exp1, expdiff; + double d0 = ST0, d1 = ST1; + + frexp(d0, &exp0); + frexp(d1, &exp1); + + env->fpus &= ~0x4700; + if (floatx80_is_zero(ST0) || floatx80_is_zero(ST1) || + floatx80_is_any_nan(ST0) || floatx80_is_any_nan(ST1) || + (isinf(d0) || isinf(d1))) { + ST0 = floatx80_modrem(ST0, ST1, mod, "ient, &env->fp_status); + } else { + expdiff = exp0 - exp1; + if (expdiff < 64) { + ST0 = floatx80_modrem(ST0, ST1, mod, "ient, &env->fp_status); + env->fpus |= (quotient & 0x4) << (8 - 2); + env->fpus |= (quotient & 0x2) << (14 - 1); + env->fpus |= (quotient & 0x1) << (9 - 0); + } else { + int n = 32 + (expdiff % 32); + double scaled = scalbn(d1, expdiff - n); + ST0 = fmod(ST0, scaled); + env->fpus |= 0x400; + } + } +#else CPU_LDoubleU temp0, temp1; int exp0, exp1, expdiff; @@ -2086,22 +2446,13 @@ static void helper_fprem_common(CPUX86State *env, bool mod) env->fpus |= (quotient & 0x2) << (14 - 1); /* (C3) <-- q1 */ env->fpus |= (quotient & 0x1) << (9 - 0); /* (C1) <-- q0 */ } else { - /* - * Partial remainder. This choice of how many bits to - * process at once is specified in AMD instruction set - * manuals, and empirically is followed by Intel - * processors as well; it ensures that the final remainder - * operation in a loop does produce the correct low three - * bits of the quotient. AMD manuals specify that the - * flags other than C2 are cleared, and empirically Intel - * processors clear them as well. - */ int n = 32 + (expdiff % 32); temp1.d = floatx80_scalbn(temp1.d, expdiff - n, &env->fp_status); ST0 = floatx80_mod(ST0, temp1.d, &env->fp_status); env->fpus |= 0x400; /* C2 <-- 1 */ } } +#endif merge_exception_flags(env, old_flags); } @@ -2119,6 +2470,7 @@ void helper_fprem(CPUX86State *env) #define log2_e_sig_high 0xb8aa3b295c17f0bbULL #define log2_e_sig_low 0xbe87fed0691d3e89ULL +#if !USE_NATIVE_DOUBLE_STORAGE /* * Polynomial coefficients for an approximation to log2((1+x)/(1-x)), * with only odd powers of x used, for x in the interval [2*sqrt(2)-3, @@ -2249,10 +2601,16 @@ static void helper_fyl2x_common(CPUX86State *env, floatx80 arg, int32_t *exp, *sig0 = asig0; *sig1 = asig1; } +#endif /* !USE_NATIVE_DOUBLE_STORAGE */ void helper_fyl2xp1(CPUX86State *env) { int old_flags = save_exception_flags(env); +#if USE_NATIVE_DOUBLE_STORAGE + ST1 = ST1 * log2(ST0 + 1.0); + fpop(env); +#else + { uint64_t arg0_sig = extractFloatx80Frac(ST0); int32_t arg0_exp = extractFloatx80Exp(ST0); bool arg0_sign = extractFloatx80Sign(ST0); @@ -2344,13 +2702,19 @@ void helper_fyl2xp1(CPUX86State *env) asig0, asig1, &env->fp_status); env->fp_status.floatx80_rounding_precision = save_prec; } + } fpop(env); +#endif merge_exception_flags(env, old_flags); } void helper_fyl2x(CPUX86State *env) { int old_flags = save_exception_flags(env); +#if USE_NATIVE_DOUBLE_STORAGE + ST1 = ST1 * log2(ST0); + fpop(env); +#else uint64_t arg0_sig = extractFloatx80Frac(ST0); int32_t arg0_exp = extractFloatx80Exp(ST0); bool arg0_sign = extractFloatx80Sign(ST0); @@ -2492,6 +2856,7 @@ void helper_fyl2x(CPUX86State *env) env->fp_status.floatx80_rounding_precision = save_prec; } fpop(env); +#endif merge_exception_flags(env, old_flags); } @@ -2607,6 +2972,27 @@ void helper_fcos(CPUX86State *env) void helper_fxam_ST0(CPUX86State *env) { +#if USE_NATIVE_DOUBLE_STORAGE + double val = ST0; + + env->fpus &= ~0x4700; + if (signbit(val)) { + env->fpus |= 0x200; /* C1 <-- 1 */ + } + + if (env->fptags[env->fpstt]) { + env->fpus |= 0x4100; /* Empty */ + return; + } + + switch (fpclassify(val)) { + case FP_NAN: env->fpus |= 0x100; break; /* NaN */ + case FP_INFINITE: env->fpus |= 0x500; break; /* Infinity */ + case FP_ZERO: env->fpus |= 0x4000; break; /* Zero */ + case FP_SUBNORMAL: env->fpus |= 0x4400; break; /* Denormal */ + case FP_NORMAL: env->fpus |= 0x400; break; /* Normal */ + } +#else CPU_LDoubleU temp; int expdif; @@ -2638,14 +3024,13 @@ void helper_fxam_ST0(CPUX86State *env) } else if (MANTD(temp) & 0x8000000000000000ULL) { env->fpus |= 0x400; } +#endif } static void do_fstenv(X86Access *ac, target_ulong ptr, int data32) { CPUX86State *env = ac->env; - int fpus, fptag, exp, i; - uint64_t mant; - CPU_LDoubleU tmp; + int fpus, fptag, i; fpus = (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11; fptag = 0; @@ -2654,6 +3039,17 @@ static void do_fstenv(X86Access *ac, target_ulong ptr, int data32) if (env->fptags[i]) { fptag |= 3; } else { +#if USE_NATIVE_DOUBLE_STORAGE + double d = env->fpregs[i].native_d; + if (d == 0.0) { + fptag |= 1; + } else if (!isnormal(d)) { + fptag |= 2; + } +#else + CPU_LDoubleU tmp; + int exp; + uint64_t mant; tmp.d = env->fpregs[i].d; exp = EXPD(tmp); mant = MANTD(tmp); @@ -2665,6 +3061,7 @@ static void do_fstenv(X86Access *ac, target_ulong ptr, int data32) /* NaNs, infinity, denormal */ fptag |= 2; } +#endif } } if (data32) { @@ -2744,8 +3141,12 @@ static void do_fsave(X86Access *ac, target_ulong ptr, int data32) ptr += 14 << data32; for (int i = 0; i < 8; i++) { +#if USE_NATIVE_DOUBLE_STORAGE + do_fstt(ac, ptr, f64_to_fx80(ST(i))); +#else floatx80 tmp = ST(i); do_fstt(ac, ptr, tmp); +#endif ptr += 10; } @@ -2769,8 +3170,12 @@ static void do_frstor(X86Access *ac, target_ulong ptr, int data32) ptr += 14 << data32; for (int i = 0; i < 8; i++) { +#if USE_NATIVE_DOUBLE_STORAGE + ST(i) = fx80_to_f64(do_fldt(ac, ptr)); +#else floatx80 tmp = do_fldt(ac, ptr); ST(i) = tmp; +#endif ptr += 10; } } diff --git a/target/i386/tcg/fpu_helper_hard.c b/target/i386/tcg/fpu_helper_hard.c index ce4abe54f2..a91a74ad8e 100644 --- a/target/i386/tcg/fpu_helper_hard.c +++ b/target/i386/tcg/fpu_helper_hard.c @@ -1,4 +1,4 @@ -#if defined(XBOX) && defined(__x86_64__) +#if defined(XBOX) && (defined(__x86_64__) || defined(__aarch64__)) #define USE_HARD_FPU 1 #include "fpu_helper.c" #endif diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index c753caf4e5..d510e78853 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -35,12 +35,28 @@ #include "exec/log.h" -static int g_use_hard_fpu; +static int g_use_fp_jit; -#if defined(XBOX) && defined(__x86_64__) +#if defined(XBOX) +struct FPUProfileCounters { + int x87_arith; + int x87_load_store; + int x87_transcendental; + int x87_stack; + int sse_arith_packed; + int sse_arith_scalar; + int sse_cmp; + int sse_cvt; + int sse_other; +}; +struct FPUProfileCounters g_fpu_profile; +int g_fpu_helper_calls; +#endif + +#if defined(XBOX) && (defined(__x86_64__) || defined(__aarch64__)) #include "ui/xemu-settings.h" #define MAP_GEN_HELPER_SOFT_HARD(name) \ - (g_use_hard_fpu ? gen_helper_##name##__hard : gen_helper_##name##__soft) + (g_use_fp_jit ? gen_helper_##name##__hard : gen_helper_##name##__soft) #define gen_helper_flds_FT0 MAP_GEN_HELPER_SOFT_HARD(flds_FT0) #define gen_helper_fldl_FT0 MAP_GEN_HELPER_SOFT_HARD(fldl_FT0) #define gen_helper_fildl_FT0 MAP_GEN_HELPER_SOFT_HARD(fildl_FT0) @@ -121,7 +137,7 @@ static int g_use_hard_fpu; #define gen_helper_fldenv MAP_GEN_HELPER_SOFT_HARD(fldenv) #define gen_helper_fsave MAP_GEN_HELPER_SOFT_HARD(fsave) #define gen_helper_frstor MAP_GEN_HELPER_SOFT_HARD(frstor) -#endif /* defined(XBOX) && defined(__x86_64__) */ +#endif /* defined(XBOX) && (__x86_64__ || __aarch64__) */ #define HELPER_H "helper.h" #include "exec/helper-info.c.inc" @@ -1129,6 +1145,41 @@ static CCPrepare gen_prepare_eflags_o(DisasContext *s, TCGv reg) return (CCPrepare) { .cond = TCG_COND_NEVER }; case CC_OP_MULB ... CC_OP_MULQ: return (CCPrepare) { .cond = TCG_COND_NE, .reg = cpu_cc_src }; + case CC_OP_ADDB ... CC_OP_ADDQ: + { + /* + * OF for ADD: overflow when both operands have the same sign + * but the result differs. dst=result, src1=cpu_cc_src. + * src2 = dst - src1. + * OF = ((src1 ^ dst) & (src2 ^ dst)) has MSB set iff overflow. + */ + MemOp size = cc_op_size(s->cc_op); + TCGv tmp = tcg_temp_new(); + if (!reg) { + reg = tcg_temp_new(); + } + tcg_gen_xor_tl(reg, cpu_cc_src, cpu_cc_dst); + tcg_gen_sub_tl(tmp, cpu_cc_dst, cpu_cc_src); + tcg_gen_xor_tl(tmp, tmp, cpu_cc_dst); + tcg_gen_and_tl(reg, reg, tmp); + return gen_prepare_sign_nz(reg, size); + } + case CC_OP_SUBB ... CC_OP_SUBQ: + { + /* + * OF for SUB: dst=result, cc_srcT=src1, cpu_cc_src=src2. + * OF = ((src1 ^ src2) & (src1 ^ dst)) has MSB set iff overflow. + */ + MemOp size = cc_op_size(s->cc_op); + TCGv tmp = tcg_temp_new(); + if (!reg) { + reg = tcg_temp_new(); + } + tcg_gen_xor_tl(reg, s->cc_srcT, cpu_cc_src); + tcg_gen_xor_tl(tmp, s->cc_srcT, cpu_cc_dst); + tcg_gen_and_tl(reg, reg, tmp); + return gen_prepare_sign_nz(reg, size); + } default: gen_compute_eflags(s); return (CCPrepare) { .cond = TCG_COND_TSTNE, .reg = cpu_cc_src, @@ -1203,6 +1254,25 @@ static CCPrepare gen_prepare_cc(DisasContext *s, int b, TCGv reg) } break; + case CC_OP_ADDB ... CC_OP_ADDQ: + /* + * For ADD: dst = src1 + src2, where cpu_cc_dst = dst, + * cpu_cc_src = src1. Carry = dst < src1 (unsigned). + */ + size = cc_op_size(s->cc_op); + switch (jcc_op) { + case JCC_BE: + /* CF | ZF: for ADD, dst < src1 || dst == 0, i.e. dst <= src1 */ + tcg_gen_ext_tl(cpu_cc_dst, cpu_cc_dst, size); + tcg_gen_ext_tl(cpu_cc_src, cpu_cc_src, size); + cc = (CCPrepare) { .cond = TCG_COND_LEU, .reg = cpu_cc_dst, + .reg2 = cpu_cc_src, .use_reg2 = true }; + break; + default: + goto slow_jcc; + } + break; + case CC_OP_LOGICB ... CC_OP_LOGICQ: /* Mostly used for test+jump */ size = s->cc_op - CC_OP_LOGICB; @@ -1614,7 +1684,14 @@ static TCGv_ptr gen_stn_ptr(int opreg) static TCGv_ptr gen_ft0_ptr(void) { TCGv_ptr ft0 = tcg_temp_new_ptr(); - tcg_gen_addi_ptr(ft0, tcg_env, offsetof(CPUX86State, ft0)); +#if defined(__aarch64__) + if (g_use_fp_jit) { + tcg_gen_addi_ptr(ft0, tcg_env, offsetof(CPUX86State, ft0_native)); + } else +#endif + { + tcg_gen_addi_ptr(ft0, tcg_env, offsetof(CPUX86State, ft0)); + } return ft0; } @@ -1729,35 +1806,61 @@ static void gen_flush_fp(DisasContext *s) } /* - * Ugly macros to handle soft FPU helper generation + * When fp_jit is enabled AND the backend supports TCG FP ops, skip the + * helper call and fall through to the TCG FP ops path (ld80f, add_f64, etc.). + * + * Both x86_64 and aarch64 backends now implement scalar FP ops. + * ARM64 sin/cos are excluded (no native insn) and always use helpers. */ -#define GEN_HELPER_FALLBACK_v_v(func) do { \ - if (!g_use_hard_fpu) { \ +#if defined(__x86_64__) || defined(__aarch64__) +#define HARD_FPU_HAS_TCG_FP_OPS 1 +#else +#define HARD_FPU_HAS_TCG_FP_OPS 0 +#endif + +/* + * Per-group bisection flags for inline FP ops. + * Set a group to 0 to force that group through helper functions. + * Set to 1 to use the inline TCG FP ops path. + * + * Note: gen_helper_fp_arith_ST0_FT0 / STN_ST0 (add/sub/mul/div/com) + * are ALWAYS inline (they bypass these macros) and work correctly. + * sin/cos always use helpers on ARM64. Only these groups are suspects. + */ +#define BISECT_GRP_STACK 1 /* fpush, fpop, fmov_*, fxchg_*, enter_mmx */ +#define BISECT_GRP_LOAD_FT 1 /* flds_FT0, fldl_FT0 (load to FT0, no push) */ +#define BISECT_GRP_LOAD_ST 1 /* flds_ST0, fldl_ST0 (push + load to ST0) */ +#define BISECT_GRP_LOAD_I 1 /* fildl_FT0/ST0, fildll_ST0, fld1, fldz (int loads + constants) */ +#define BISECT_GRP_STORE 1 /* fsts_*, fstl_*, fistl_*, fistll_* */ +#define BISECT_GRP_UNARY 1 /* fchs, fabs, fsqrt */ + +#define GEN_HELPER_FALLBACK_v_v(func, grp) do { \ + if (!g_use_fp_jit || !HARD_FPU_HAS_TCG_FP_OPS || !(grp)) { \ gen_helper_ ## func(tcg_env); \ return; \ }} while(0) -#define GEN_HELPER_FALLBACK_v_i(func, arg) do { \ - if (!g_use_hard_fpu) { \ +#define GEN_HELPER_FALLBACK_v_i(func, arg, grp) do { \ + if (!g_use_fp_jit || !HARD_FPU_HAS_TCG_FP_OPS || !(grp)) { \ gen_helper_ ## func(tcg_env, tcg_constant_i32(arg)); \ return; \ }} while(0) -#define GEN_HELPER_FALLBACK_v_T(func, arg) do { \ - if (!g_use_hard_fpu) { \ +#define GEN_HELPER_FALLBACK_v_T(func, arg, grp) do { \ + if (!g_use_fp_jit || !HARD_FPU_HAS_TCG_FP_OPS || !(grp)) { \ gen_helper_ ## func(tcg_env, arg); \ return; \ }} while(0) -#define GEN_HELPER_FALLBACK_T_v(func, arg) do { \ - if (!g_use_hard_fpu) { \ +#define GEN_HELPER_FALLBACK_T_v(func, arg, grp) do { \ + if (!g_use_fp_jit || !HARD_FPU_HAS_TCG_FP_OPS || !(grp)) { \ gen_helper_ ## func(arg, tcg_env); \ return; \ }} while(0) static void gen_fpush(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fpush); + GEN_HELPER_FALLBACK_v_v(fpush, BISECT_GRP_STACK); tcg_gen_subi_i32(fpstt, fpstt, 1); tcg_gen_andi_i32(fpstt, fpstt, 7); @@ -1768,7 +1871,7 @@ static void gen_fpush(DisasContext *s) static void gen_fpop(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fpop); + GEN_HELPER_FALLBACK_v_v(fpop, BISECT_GRP_STACK); gen_set_fptag(0, 1); /* invalidate stack entry */ tcg_gen_addi_i32(fpstt, fpstt, 1); @@ -1780,25 +1883,25 @@ static void gen_fpop(DisasContext *s) static void gen_fmov_FT0_STN(DisasContext *s, int st_index) { - GEN_HELPER_FALLBACK_v_i(fmov_FT0_STN, st_index); + GEN_HELPER_FALLBACK_v_i(fmov_FT0_STN, st_index, BISECT_GRP_STACK); fp_pc_wrapper(gen_fmov_FT0_STN)(s, st_index); } static void gen_fmov_ST0_STN(DisasContext *s, int st_index) { - GEN_HELPER_FALLBACK_v_i(fmov_ST0_STN, st_index); + GEN_HELPER_FALLBACK_v_i(fmov_ST0_STN, st_index, BISECT_GRP_STACK); fp_pc_wrapper(gen_fmov_ST0_STN)(s, st_index); } static void gen_fmov_STN_ST0(DisasContext *s, int st_index) { - GEN_HELPER_FALLBACK_v_i(fmov_STN_ST0, st_index); + GEN_HELPER_FALLBACK_v_i(fmov_STN_ST0, st_index, BISECT_GRP_STACK); fp_pc_wrapper(gen_fmov_STN_ST0)(s, st_index); } static void gen_fxchg_ST0_STN(DisasContext *s, int st_index) { - GEN_HELPER_FALLBACK_v_i(fxchg_ST0_STN, st_index); + GEN_HELPER_FALLBACK_v_i(fxchg_ST0_STN, st_index, BISECT_GRP_STACK); /* Ensure ST0, STN are loaded */ if (fpu_using_double_precision(s)) { @@ -1816,7 +1919,7 @@ static void gen_fxchg_ST0_STN(DisasContext *s, int st_index) static void gen_enter_mmx(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(enter_mmx); + GEN_HELPER_FALLBACK_v_v(enter_mmx, BISECT_GRP_STACK); gen_flush_fp((DisasContext *)tcg_ctx->disas_ctx); @@ -1830,89 +1933,89 @@ static void gen_enter_mmx(DisasContext *s) static void gen_flds_FT0(DisasContext *s, TCGv_i32 arg) { - GEN_HELPER_FALLBACK_v_T(flds_FT0, arg); + GEN_HELPER_FALLBACK_v_T(flds_FT0, arg, BISECT_GRP_LOAD_FT); fp_pc_wrapper(gen_flds_FT0)(s, arg); } static void gen_flds_ST0(DisasContext *s, TCGv_i32 arg) { - GEN_HELPER_FALLBACK_v_T(flds_ST0, arg); + GEN_HELPER_FALLBACK_v_T(flds_ST0, arg, BISECT_GRP_LOAD_ST); gen_fpush(s); fp_pc_wrapper(gen_flds_ST0)(s, arg); } static void gen_fldl_FT0(DisasContext *s, TCGv_i64 arg) { - GEN_HELPER_FALLBACK_v_T(fldl_FT0, arg); + GEN_HELPER_FALLBACK_v_T(fldl_FT0, arg, BISECT_GRP_LOAD_FT); fp_pc_wrapper(gen_fldl_FT0)(s, arg); } static void gen_fldl_ST0(DisasContext *s, TCGv_i64 arg) { - GEN_HELPER_FALLBACK_v_T(fldl_ST0, arg); + GEN_HELPER_FALLBACK_v_T(fldl_ST0, arg, BISECT_GRP_LOAD_ST); gen_fpush(s); fp_pc_wrapper(gen_fldl_ST0)(s, arg); } static void gen_fildl_FT0(DisasContext *s, TCGv_i32 arg) { - GEN_HELPER_FALLBACK_v_T(fildl_FT0, arg); + GEN_HELPER_FALLBACK_v_T(fildl_FT0, arg, BISECT_GRP_LOAD_I); fp_pc_wrapper(gen_fildl_FT0)(s, arg); } static void gen_fildl_ST0(DisasContext *s, TCGv_i32 arg) { - GEN_HELPER_FALLBACK_v_T(fildl_ST0, arg); + GEN_HELPER_FALLBACK_v_T(fildl_ST0, arg, BISECT_GRP_LOAD_I); gen_fpush(s); fp_pc_wrapper(gen_fildl_ST0)(s, arg); } static void gen_fildll_ST0(DisasContext *s, TCGv_i64 arg) { - GEN_HELPER_FALLBACK_v_T(fildll_ST0, arg); + GEN_HELPER_FALLBACK_v_T(fildll_ST0, arg, BISECT_GRP_LOAD_I); gen_fpush(s); fp_pc_wrapper(gen_fildll_ST0)(s, arg); } static void gen_fsts_ST0(DisasContext *s, TCGv_i32 arg) { - GEN_HELPER_FALLBACK_T_v(fsts_ST0, arg); + GEN_HELPER_FALLBACK_T_v(fsts_ST0, arg, BISECT_GRP_STORE); fp_pc_wrapper(gen_fsts_ST0)(s, arg); } static void gen_fstl_ST0(DisasContext *s, TCGv_i64 arg) { - GEN_HELPER_FALLBACK_T_v(fstl_ST0, arg); + GEN_HELPER_FALLBACK_T_v(fstl_ST0, arg, BISECT_GRP_STORE); fp_pc_wrapper(gen_fstl_ST0)(s, arg); } static void gen_fistl_ST0(DisasContext *s, TCGv_i32 arg) { - GEN_HELPER_FALLBACK_T_v(fistl_ST0, arg); + GEN_HELPER_FALLBACK_T_v(fistl_ST0, arg, BISECT_GRP_STORE); fp_pc_wrapper(gen_fistl_ST0)(s, arg); } static void gen_fistll_ST0(DisasContext *s, TCGv_i64 arg) { - GEN_HELPER_FALLBACK_T_v(fistll_ST0, arg); + GEN_HELPER_FALLBACK_T_v(fistll_ST0, arg, BISECT_GRP_STORE); fp_pc_wrapper(gen_fistll_ST0)(s, arg); } static void gen_fchs_ST0(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fchs_ST0); + GEN_HELPER_FALLBACK_v_v(fchs_ST0, BISECT_GRP_UNARY); fp_pc_wrapper(gen_fchs_ST0)(s); } static void gen_fabs_ST0(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fabs_ST0); + GEN_HELPER_FALLBACK_v_v(fabs_ST0, BISECT_GRP_UNARY); fp_pc_wrapper(gen_fabs_ST0)(s); } static void gen_fsqrt(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fsqrt); + GEN_HELPER_FALLBACK_v_v(fsqrt, BISECT_GRP_UNARY); fp_pc_wrapper(gen_fsqrt)(s); } @@ -1926,21 +2029,36 @@ static void gen_clear_fpus_c2(DisasContext *s) static void gen_fsin(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fsin); + /* + * ARM64 has no native sin instruction, so always use helper even when + * HARD_FPU_HAS_TCG_FP_OPS is set. The helper uses __hard (native double) + * when fp_jit is enabled, which is fast enough for the rare FSIN. + */ +#ifndef __x86_64__ + gen_helper_fsin(tcg_env); + gen_clear_fpus_c2(s); + return; +#endif + GEN_HELPER_FALLBACK_v_v(fsin, BISECT_GRP_UNARY); fp_pc_wrapper(gen_fsin)(s); gen_clear_fpus_c2(s); /* FIXME: Does not check range correctly */ } static void gen_fcos(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fcos); +#ifndef __x86_64__ + gen_helper_fcos(tcg_env); + gen_clear_fpus_c2(s); + return; +#endif + GEN_HELPER_FALLBACK_v_v(fcos, BISECT_GRP_UNARY); fp_pc_wrapper(gen_fcos)(s); gen_clear_fpus_c2(s); /* FIXME: Does not check range correctly */ } static void gen_helper_fp_arith_ST0_FT0(DisasContext *s, int op) { - if (g_use_hard_fpu) { + if (g_use_fp_jit && HARD_FPU_HAS_TCG_FP_OPS) { fp_pc_wrapper(gen_helper_fp_arith_ST0_FT0)(s, op); } else { switch (op) { @@ -1980,7 +2098,7 @@ static void gen_fcom_ST0_FT0(DisasContext *s) /* NOTE the exception in "r" op ordering */ static void gen_helper_fp_arith_STN_ST0(DisasContext *s, int op, int opreg) { - if (g_use_hard_fpu) { + if (g_use_fp_jit && HARD_FPU_HAS_TCG_FP_OPS) { fp_pc_wrapper(gen_helper_fp_arith_STN_ST0)(s, op, opreg); } else { TCGv_i32 tmp = tcg_constant_i32(opreg); @@ -2010,20 +2128,20 @@ static void gen_helper_fp_arith_STN_ST0(DisasContext *s, int op, int opreg) static void gen_fld1_ST0(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fld1_ST0); + GEN_HELPER_FALLBACK_v_v(fld1_ST0, BISECT_GRP_LOAD_I); fp_pc_wrapper(gen_fld1_ST0)(s); } static void gen_fldz_ST0(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fldz_ST0); + GEN_HELPER_FALLBACK_v_v(fldz_ST0, BISECT_GRP_LOAD_I); fp_pc_wrapper(gen_fldz_ST0)(s); /* FIXME: Set tag word */ } static void gen_fldz_FT0(DisasContext *s) { - GEN_HELPER_FALLBACK_v_v(fldz_FT0); + GEN_HELPER_FALLBACK_v_v(fldz_FT0, BISECT_GRP_LOAD_I); fp_pc_wrapper(gen_fldz_FT0)(s); } @@ -2937,6 +3055,54 @@ static void gen_x87(DisasContext *s, X86DecodedInsn *decode) int modrm = s->modrm; int mod, rm, op; +#if defined(XBOX) + { + int xop = ((b & 7) << 3) | ((modrm >> 3) & 7); + int xmod = (modrm >> 6) & 3; + if (xmod != 3) { + switch (xop) { + case 0x00 ... 0x07: case 0x10 ... 0x17: + case 0x20 ... 0x27: case 0x30 ... 0x37: + g_fpu_profile.x87_arith++; + break; + case 0x08: case 0x0a: case 0x0b: + case 0x18 ... 0x1b: case 0x28 ... 0x2b: + case 0x38 ... 0x3b: case 0x1d: case 0x1f: + case 0x3d: case 0x3f: + g_fpu_profile.x87_load_store++; + break; + default: + g_fpu_profile.x87_stack++; + break; + } + } else { + switch (xop) { + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04 ... 0x07: + case 0x15: case 0x1d: case 0x1e: + case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24 ... 0x27: + case 0x2c: case 0x2d: + case 0x30: case 0x31: case 0x32: + case 0x34 ... 0x37: + g_fpu_profile.x87_arith++; + break; + case 0x08: case 0x0b: case 0x2a: case 0x2b: + case 0x3a: case 0x3b: + g_fpu_profile.x87_load_store++; + break; + case 0x0e: + case 0x0f: + g_fpu_profile.x87_transcendental++; + break; + default: + g_fpu_profile.x87_stack++; + break; + } + } + } +#endif + if (s->flags & (HF_EM_MASK | HF_TS_MASK)) { /* if CR0.EM or CR0.TS are set, generate an FPU exception */ /* XXX: what to do if illegal op ? */ @@ -4227,8 +4393,8 @@ void tcg_x86_init(void) fpstt = tcg_global_mem_new_i32(tcg_env, offsetof(CPUX86State, fpstt), "fpstt"); -#if defined(XBOX) && defined(__x86_64__) - g_use_hard_fpu = g_config.perf.hard_fpu; +#if defined(XBOX) && (defined(__x86_64__) || defined(__aarch64__)) + g_use_fp_jit = g_config.perf.fp_jit; #endif } diff --git a/tcg/aarch64/tcg-target-con-set.h b/tcg/aarch64/tcg-target-con-set.h index d0622e65fb..70c1f635f0 100644 --- a/tcg/aarch64/tcg-target-con-set.h +++ b/tcg/aarch64/tcg-target-con-set.h @@ -15,6 +15,7 @@ C_O0_I2(rz, r) C_O0_I2(w, r) C_O0_I3(rz, rz, r) C_O1_I1(r, r) +C_O1_I1(r, w) C_O1_I1(w, r) C_O1_I1(w, w) C_O1_I1(w, wr) @@ -28,6 +29,7 @@ C_O1_I2(r, rZ, rA) C_O1_I2(r, rz, rMZ) C_O1_I2(r, rz, rz) C_O1_I2(r, rZ, rZ) +C_O1_I2(r, w, w) C_O1_I2(w, 0, w) C_O1_I2(w, w, w) C_O1_I2(w, w, wN) diff --git a/tcg/aarch64/tcg-target-has.h b/tcg/aarch64/tcg-target-has.h index 69e83efb69..44afce2f3a 100644 --- a/tcg/aarch64/tcg-target-has.h +++ b/tcg/aarch64/tcg-target-has.h @@ -53,6 +53,8 @@ #define TCG_TARGET_HAS_cmpsel_vec 0 #define TCG_TARGET_HAS_tst_vec 1 +#define TCG_TARGET_HAS_fpu 1 + #define TCG_TARGET_extract_valid(type, ofs, len) 1 #define TCG_TARGET_sextract_valid(type, ofs, len) 1 #define TCG_TARGET_deposit_valid(type, ofs, len) 1 diff --git a/tcg/aarch64/tcg-target.c.inc b/tcg/aarch64/tcg-target.c.inc index 7d302d7948..f557235ddb 100644 --- a/tcg/aarch64/tcg-target.c.inc +++ b/tcg/aarch64/tcg-target.c.inc @@ -46,7 +46,10 @@ static const char * const tcg_target_reg_names[TCG_TARGET_NB_REGS] = { static const int tcg_target_reg_alloc_order[] = { TCG_REG_X20, TCG_REG_X21, TCG_REG_X22, TCG_REG_X23, - TCG_REG_X24, TCG_REG_X25, TCG_REG_X26, TCG_REG_X27, + TCG_REG_X24, TCG_REG_X25, TCG_REG_X26, +#ifndef XBOX + TCG_REG_X27, /* reserved for xbox_ram_fp struct pointer when XBOX */ +#endif TCG_REG_X28, /* we will reserve this for guest_base if configured */ TCG_REG_X8, TCG_REG_X9, TCG_REG_X10, TCG_REG_X11, @@ -90,6 +93,11 @@ static TCGReg tcg_target_call_oarg_reg(TCGCallReturnKind kind, int slot) #define TCG_REG_GUEST_BASE TCG_REG_X28 +#ifdef XBOX +#define TCG_REG_XBOX_RAM_BASE TCG_REG_X27 +#define TCG_REG_XBOX_HOST_BASE TCG_REG_X26 +#endif + static bool reloc_pc26(tcg_insn_unit *src_rw, const tcg_insn_unit *target) { const tcg_insn_unit *src_rx = tcg_splitwx_to_rx(src_rw); @@ -640,6 +648,53 @@ typedef enum { I3617_ABS = 0x0e20b800, I3617_NEG = 0x2e20b800, + /* Scalar FP data-processing (2 source) -- Rd, Rn, Rm */ + I_FP2_FMUL_S = 0x1e200800, + I_FP2_FDIV_S = 0x1e201800, + I_FP2_FADD_S = 0x1e202800, + I_FP2_FSUB_S = 0x1e203800, + I_FP2_FMUL_D = 0x1e600800, + I_FP2_FDIV_D = 0x1e601800, + I_FP2_FADD_D = 0x1e602800, + I_FP2_FSUB_D = 0x1e603800, + + /* Scalar FP data-processing (1 source) -- Rd, Rn */ + I_FP1_FMOV_S = 0x1e204000, + I_FP1_FABS_S = 0x1e20c000, + I_FP1_FNEG_S = 0x1e214000, + I_FP1_FSQRT_S = 0x1e21c000, + I_FP1_FCVT_DS = 0x1e22c000, /* FCVT Dd, Sn (single->double) */ + I_FP1_FMOV_D = 0x1e604000, + I_FP1_FABS_D = 0x1e60c000, + I_FP1_FNEG_D = 0x1e614000, + I_FP1_FSQRT_D = 0x1e61c000, + I_FP1_FCVT_SD = 0x1e624000, /* FCVT Sd, Dn (double->single) */ + + /* Scalar FP compare -- Rn, Rm */ + I_FPCMP_FCMP_S = 0x1e202000, + I_FPCMP_FCMP_D = 0x1e602000, + + /* FP<->integer conversions */ + I_FPCVT_SCVTF_SW = 0x1e220000, /* SCVTF Sd, Wn */ + I_FPCVT_SCVTF_DW = 0x1e620000, /* SCVTF Dd, Wn */ + I_FPCVT_SCVTF_SX = 0x9e220000, /* SCVTF Sd, Xn */ + I_FPCVT_SCVTF_DX = 0x9e620000, /* SCVTF Dd, Xn */ + I_FPCVT_FCVTZS_WS = 0x1e380000, /* FCVTZS Wn, Sd */ + I_FPCVT_FCVTZS_WD = 0x1e780000, /* FCVTZS Wn, Dd */ + I_FPCVT_FCVTZS_XS = 0x9e380000, /* FCVTZS Xn, Sd */ + I_FPCVT_FCVTZS_XD = 0x9e780000, /* FCVTZS Xn, Dd */ + + /* FP<->integer bit-copy (FMOV between GPR and FP reg) */ + I_FPMOV_W_S = 0x1e260000, /* FMOV Wd, Sn */ + I_FPMOV_S_W = 0x1e270000, /* FMOV Sd, Wn */ + I_FPMOV_X_D = 0x9e660000, /* FMOV Xd, Dn */ + I_FPMOV_D_X = 0x9e670000, /* FMOV Dd, Xn */ + + /* System: read NZCV flags into GPR */ + I_MRS_NZCV = 0xd53b4200, /* MRS Xt, NZCV */ + /* System: write FPCR */ + I_MSR_FPCR = 0xd51b4400, /* MSR FPCR, Xt */ + /* System instructions. */ NOP = 0xd503201f, DMB_ISH = 0xd50338bf, @@ -1251,6 +1306,15 @@ static bool tcg_out_mov(TCGContext *s, TCGType type, TCGReg ret, TCGReg arg) tcg_out_insn(s, 3616, ORR, 1, 0, ret, arg, arg); break; + case TCG_TYPE_F32: + tcg_debug_assert(ret >= 32 && arg >= 32); + tcg_out32(s, I_FP1_FMOV_S | ((arg & 0x1f) << 5) | (ret & 0x1f)); + break; + case TCG_TYPE_F64: + tcg_debug_assert(ret >= 32 && arg >= 32); + tcg_out32(s, I_FP1_FMOV_D | ((arg & 0x1f) << 5) | (ret & 0x1f)); + break; + default: g_assert_not_reached(); } @@ -1280,6 +1344,14 @@ static void tcg_out_ld(TCGContext *s, TCGType type, TCGReg ret, insn = I3312_LDRVQ; lgsz = 4; break; + case TCG_TYPE_F32: + insn = I3312_LDRVS; + lgsz = 2; + break; + case TCG_TYPE_F64: + insn = I3312_LDRVD; + lgsz = 3; + break; default: g_assert_not_reached(); } @@ -1309,6 +1381,14 @@ static void tcg_out_st(TCGContext *s, TCGType type, TCGReg src, insn = I3312_STRVQ; lgsz = 4; break; + case TCG_TYPE_F32: + insn = I3312_STRVS; + lgsz = 2; + break; + case TCG_TYPE_F64: + insn = I3312_STRVD; + lgsz = 3; + break; default: g_assert_not_reached(); } @@ -1662,6 +1742,82 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, unsigned mem_index = get_mmuidx(oi); TCGReg addr_adj; uint64_t compare_mask; +#ifdef XBOX + tcg_insn_unit *xbox_fast_done = NULL; + tcg_insn_unit *xbox_vram_done = NULL; + + if (is_ld && xbox_ram_size) { + uint64_t ram_mask = ~(xbox_ram_size - 1); + if (addr_type == TCG_TYPE_I32) { + ram_mask &= 0xFFFFFFFFULL; + } + + tcg_insn_unit *label_bne; + + /* 1. CBZ X26, →tlb (X26=0 means preamble disabled fast path) */ + tcg_insn_unit *label_gate_cbz = s->code_ptr; + tcg_out_insn(s, 3201, CBZ, 1, TCG_REG_XBOX_HOST_BASE, 0); + + /* 2. TST addr, #~(ram_size-1): zero if addr within RAM */ + tcg_out_logicali(s, I3404_ANDSI, addr_type, + TCG_REG_XZR, addr_reg, ram_mask); + + /* 3. B.NE →vram_check (addr outside RAM range) */ + label_bne = s->code_ptr; + tcg_out_insn(s, 3202, B_C, TCG_COND_NE, 0); + + /* 4. MOV TMP1, X26 (X26 holds host_base from preamble) */ + tcg_out_mov(s, TCG_TYPE_PTR, TCG_REG_TMP1, + TCG_REG_XBOX_HOST_BASE); + + /* 5. B →done (skip TLB code) */ + xbox_fast_done = s->code_ptr; + tcg_out_insn(s, 3206, B, 0); + + /* + * VRAM fast path: label_bne (RAM range miss) lands here. + * X27 still points to &xbox_ram_fp so we can load vram_pci_base. + */ + tcg_insn_unit *label_vram_cbz, *label_vram_bne; + + reloc_pc19(label_bne, tcg_splitwx_to_rx(s->code_ptr)); + + /* V1. LDR X_TMP0, [X27, #vram_pci_base] */ + tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP0, + TCG_REG_XBOX_RAM_BASE, + offsetof(XboxRamFPState, vram_pci_base)); + + /* V2. CBZ X_TMP0, →tlb (not configured) */ + label_vram_cbz = s->code_ptr; + tcg_out_insn(s, 3201, CBZ, 1, TCG_REG_TMP0, 0); + + /* V3. SUB TMP2 = addr - vram_base (offset into VRAM) */ + tcg_out_insn(s, 3502, SUB, 1, + TCG_REG_TMP2, addr_reg, TCG_REG_TMP0); + + /* V4. TST offset, #~(ram_size-1): zero if within range */ + tcg_out_logicali(s, I3404_ANDSI, addr_type, + TCG_REG_XZR, TCG_REG_TMP2, ram_mask); + + /* V5. B.NE →tlb (outside VRAM range) */ + label_vram_bne = s->code_ptr; + tcg_out_insn(s, 3202, B_C, TCG_COND_NE, 0); + + /* V6. SUB TMP1 = X26 - TMP0 (host_base - vram_base) */ + tcg_out_insn(s, 3502, SUB, 1, + TCG_REG_TMP1, TCG_REG_XBOX_HOST_BASE, + TCG_REG_TMP0); + + /* V7. B →done */ + xbox_vram_done = s->code_ptr; + tcg_out_insn(s, 3206, B, 0); + + /* Patch fallback branches to TLB start */ + reloc_pc19(label_gate_cbz, tcg_splitwx_to_rx(s->code_ptr)); + reloc_pc19(label_vram_cbz, tcg_splitwx_to_rx(s->code_ptr)); + reloc_pc19(label_vram_bne, tcg_splitwx_to_rx(s->code_ptr)); + } +#endif ldst = new_ldst_label(s); ldst->is_ld = is_ld; @@ -1719,6 +1875,15 @@ static TCGLabelQemuLdst *prepare_host_addr(TCGContext *s, HostAddress *h, h->base = TCG_REG_TMP1; h->index = addr_reg; h->index_ext = addr_type; + +#ifdef XBOX + if (xbox_fast_done) { + reloc_pc26(xbox_fast_done, tcg_splitwx_to_rx(s->code_ptr)); + } + if (xbox_vram_done) { + reloc_pc26(xbox_vram_done, tcg_splitwx_to_rx(s->code_ptr)); + } +#endif } else { if (a_mask) { ldst = new_ldst_label(s); @@ -2893,11 +3058,584 @@ static const TCGOutOpStore outop_st = { .out_r = tcg_out_st, }; -static inline void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, - const TCGArg args[TCG_MAX_OP_ARGS], - const int const_args[TCG_MAX_OP_ARGS]) +/* + * Bisection flags for ARM64 TCG FP ops. + * Set to 0 to stub out the group and isolate bugs. + * When stubbed: flcr=NOP, ld80f=load zero, st80f=NOP. + */ +#define BISECT_FP_FLCR 1 /* FP control register write */ +#define BISECT_FP_LD80F 1 /* floatx80 -> double load */ +#define BISECT_FP_ST80F 1 /* double -> floatx80 store */ +#define BISECT_FP_ARITH 1 /* add/sub/mul/div/sqrt/abs/chs/mov */ +#define BISECT_FP_COM 1 /* compare (FCMP + flag conversion) */ +#define BISECT_FP_CVT 1 /* int<->FP conversions, FP<->FP conversions */ +#define BISECT_FP_MOVIF 1 /* bit-copy FMOV between int and FP regs */ + +/* + * Scalar FP instruction emission helpers. + * V registers are encoded using their low 5 bits (V0-V31 = TCG_REG_V0-V31). + */ +static void tcg_out_fp2src(TCGContext *s, AArch64Insn insn, + TCGReg rd, TCGReg rn, TCGReg rm) { - g_assert_not_reached(); + tcg_out32(s, insn | (rm & 0x1f) << 16 | (rn & 0x1f) << 5 | (rd & 0x1f)); +} + +static void tcg_out_fp1src(TCGContext *s, AArch64Insn insn, + TCGReg rd, TCGReg rn) +{ + tcg_out32(s, insn | (rn & 0x1f) << 5 | (rd & 0x1f)); +} + +static void tcg_out_fpcmp(TCGContext *s, AArch64Insn insn, + TCGReg rn, TCGReg rm) +{ + tcg_out32(s, insn | (rm & 0x1f) << 16 | (rn & 0x1f) << 5); +} + +static void tcg_out_fpcvt(TCGContext *s, AArch64Insn insn, + TCGReg rd, TCGReg rn) +{ + tcg_out32(s, insn | (rn & 0x1f) << 5 | (rd & 0x1f)); +} + +/* + * Emit FCMP + flag conversion to x86-style EFLAGS (CF, PF, ZF) in a GPR. + * + * ARM64 FCMP NZCV results: + * a > b: N=0 Z=0 C=1 V=0 + * a < b: N=1 Z=0 C=0 V=0 + * a == b: N=0 Z=1 C=1 V=0 + * unord: N=0 Z=0 C=1 V=1 + * + * x86 COMISD/COMISS flags (CF=bit0, PF=bit2, ZF=bit6): + * a > b: 0x00 + * a < b: 0x01 + * a == b: 0x40 + * unord: 0x45 + * + * Mapping: x86_CF = N|V, x86_PF = V, x86_ZF = Z + */ +static void tcg_out_fp_com(TCGContext *s, AArch64Insn cmp_insn, + TCGReg rd, TCGReg rn, TCGReg rm) +{ + TCGReg t0 = TCG_REG_TMP0; + TCGReg t1 = TCG_REG_TMP1; + + /* FCMP Sn/Dn, Sm/Dm -- sets NZCV */ + tcg_out_fpcmp(s, cmp_insn, rn, rm); + + /* MRS t0, NZCV */ + tcg_out32(s, I_MRS_NZCV | t0); + + /* UBFX t0, t0, #28, #4 -> t0 = {N(3),Z(2),C(1),V(0)} */ + tcg_out_ubfm(s, TCG_TYPE_I64, t0, t0, 28, 31); + + /* + * Map ARM64 FCMP NZCV to x86 EFLAGS {CF, PF, ZF}: + * CF (bit 0) = N | V (less-than or unordered) + * PF (bit 2) = V (unordered / NaN) + * ZF (bit 6) = Z | V (equal or unordered) + * + * t0 = {N(3), Z(2), C(1), V(0)} after UBFX above + */ + + /* t1 = V (bit 0 of t0) */ + tcg_out_ubfm(s, TCG_TYPE_I64, t1, t0, 0, 0); + + /* rd = N (bit 3 of t0 -> bit 0) */ + tcg_out_ubfm(s, TCG_TYPE_I64, rd, t0, 3, 3); + + /* rd = N | V -> CF at bit 0 */ + tcg_out_insn(s, 3510, ORR, TCG_TYPE_I64, rd, rd, t1); + + /* rd |= (V << 2) -> PF at bit 2 */ + tcg_out_insn_3502S(s, I3510_ORR, TCG_TYPE_I64, rd, rd, t1, 2); + + /* t1 = Z | V for ZF: extract Z, OR with V (still in t0 bit 0) */ + tcg_out_ubfm(s, TCG_TYPE_I64, t1, t0, 2, 2); /* t1 = Z */ + tcg_out_ubfm(s, TCG_TYPE_I64, t0, t0, 0, 0); /* t0 = V (re-extract) */ + tcg_out_insn(s, 3510, ORR, TCG_TYPE_I64, t1, t1, t0); /* t1 = Z | V */ + + /* rd |= ((Z|V) << 6) -> ZF at bit 6 */ + tcg_out_insn_3502S(s, I3510_ORR, TCG_TYPE_I64, rd, rd, t1, 6); +} + +/* + * ld80f: Load 10-byte floatx80 from memory, convert to f64 (or f32). + * + * floatx80 layout: [mantissa: 8 bytes @ +0][sign|exp: 2 bytes @ +8] + * + * Conversion to IEEE 754 double (normal case): + * d_exp = exp_80 - 15360 (rebias: 16383 -> 1023) + * d_mant = mant[62:11] (drop integer bit, keep top 52) + * double = (sign << 63) | (d_exp << 52) | d_mant + * + * Special cases (inf/NaN/zero/denorm) produce +0.0 for simplicity. + * This is acceptable since fp_jit already implies 64-bit precision loss. + * + * Uses scratch: TCG_REG_TMP0 (X16), TCG_REG_TMP1 (X17). + */ +static void tcg_out_ld80f(TCGContext *s, bool dp, TCGReg rd_fp, TCGReg rn_ptr) +{ + TCGReg r0 = TCG_REG_TMP0; + TCGReg r1 = TCG_REG_TMP1; + tcg_insn_unit *br_special, *br_done; + + /* r0 = mantissa (8 bytes), r1 = sign_exp (2 bytes) */ + tcg_out_ldst(s, I3312_LDRX, r0, rn_ptr, 0, 3); + tcg_out_ldst(s, I3312_LDRH, r1, rn_ptr, 8, 1); + + /* d_mant = mant[62:11] in bits [51:0] */ + tcg_out_ubfm(s, TCG_TYPE_I64, r0, r0, 11, 62); + + /* exp = sign_exp[14:0] (clobbers sign in r1) */ + tcg_out_ubfm(s, TCG_TYPE_I32, r1, r1, 0, 14); + + /* d_exp = exp - 15360 (= exp - 12288 - 3072) */ + tcg_out_insn_3401(s, I3401_SUBI, TCG_TYPE_I32, r1, r1, 12288); + tcg_out_insn_3401(s, I3401_SUBI, TCG_TYPE_I32, r1, r1, 3072); + + /* If d_exp < 1 or d_exp > 0x7FE -> special (zero/denorm/inf/NaN). + * CMP W(r1), #0x7FF; B.HS -> special (unsigned d_exp >= 0x7FF) */ + tcg_out_insn_3401(s, I3401_SUBSI, TCG_TYPE_I32, TCG_REG_XZR, r1, 0x7FF); + br_special = s->code_ptr; + tcg_out_insn(s, 3202, B_C, TCG_COND_GEU, 0); /* placeholder offset */ + + /* CBZ W(r1), special (d_exp == 0 -> zero/denorm) */ + tcg_out_insn(s, 3201, CBZ, TCG_TYPE_I32, r1, 0); /* placeholder */ + + /* --- Normal path --- */ + /* LSL r1, r1, #52: shift d_exp into position */ + tcg_out_ubfm(s, TCG_TYPE_I64, r1, r1, 12, 11); + + /* r0 = d_mant | (d_exp << 52) */ + tcg_out_insn(s, 3510, ORR, TCG_TYPE_I64, r0, r0, r1); + + /* Reload sign_exp for sign extraction */ + tcg_out_ldst(s, I3312_LDRH, r1, rn_ptr, 8, 1); + + /* UBFX r1, r1, #15, #1 -> r1 = sign bit (0 or 1) */ + tcg_out_ubfm(s, TCG_TYPE_I64, r1, r1, 15, 15); + + /* LSL r1, r1, #63 -> sign at bit 63 */ + tcg_out_ubfm(s, TCG_TYPE_I64, r1, r1, 1, 0); + + /* r0 |= sign */ + tcg_out_insn(s, 3510, ORR, TCG_TYPE_I64, r0, r0, r1); + + /* FMOV Dd, X(r0) */ + tcg_out32(s, I_FPMOV_D_X | (r0 & 0x1f) << 5 | (rd_fp & 0x1f)); + + if (!dp) { + tcg_out32(s, I_FP1_FCVT_SD | ((rd_fp & 0x1f) << 5) + | (rd_fp & 0x1f)); + } + + /* B .Ldone */ + br_done = s->code_ptr; + tcg_out32(s, I3206_B); /* placeholder */ + + /* --- Special path: emit +0.0 (acceptable for fp_jit) --- */ + { + tcg_insn_unit *special_start = s->code_ptr; + + /* FMOV Dd, XZR (zero) */ + tcg_out32(s, I_FPMOV_D_X | (TCG_REG_XZR & 0x1f) << 5 + | (rd_fp & 0x1f)); + + /* Patch br_special (B.HS) to jump here */ + ptrdiff_t off1 = special_start - br_special; + *br_special = deposit32(*br_special, 5, 19, off1); + + /* Patch CBZ to jump here */ + tcg_insn_unit *cbz_insn = br_special + 1; + ptrdiff_t off2 = special_start - cbz_insn; + *cbz_insn = deposit32(*cbz_insn, 5, 19, off2); + } + + /* Patch br_done to jump here */ + { + tcg_insn_unit *done = s->code_ptr; + ptrdiff_t off = done - br_done; + *br_done = deposit32(*br_done, 0, 26, off); + } +} + +/* + * st80f: Convert f64 (or f32) from FP register to 10-byte floatx80 in memory. + * + * Reverse of ld80f: + * exp_80 = d_exp + 15360 + * mant = (1 << 63) | (d_mant << 11) + * sign_exp = (sign << 15) | exp_80 + * + * Uses scratch: TCG_REG_TMP0 (X16), TCG_REG_TMP1 (X17). + */ +static void tcg_out_st80f(TCGContext *s, bool dp, TCGReg rn_fp, TCGReg rd_ptr) +{ + TCGReg r0 = TCG_REG_TMP0; + TCGReg r1 = TCG_REG_TMP1; + + if (!dp) { + /* Promote f32 to f64 in-place */ + tcg_out32(s, I_FP1_FCVT_DS | ((rn_fp & 0x1f) << 5) + | (rn_fp & 0x1f)); + } + + /* FMOV X(r0), Dn -> r0 = double bit pattern */ + tcg_out32(s, I_FPMOV_X_D | (rn_fp & 0x1f) << 5 | (r0 & 0x1f)); + + /* --- Build sign_exp (16-bit) in r1 --- */ + /* Extract d_exp = bits[62:52] (11 bits) */ + tcg_out_ubfm(s, TCG_TYPE_I64, r1, r0, 52, 62); + + /* exp_80 = d_exp + 15360 (= d_exp + 12288 + 3072) */ + tcg_out_insn_3401(s, I3401_ADDI, TCG_TYPE_I32, r1, r1, 12288); + tcg_out_insn_3401(s, I3401_ADDI, TCG_TYPE_I32, r1, r1, 3072); + + /* Insert sign bit: sign_exp[15] = double[63]. + * BFI r1, r0_sign, #15, #1: insert bit from r0. + * First get sign into r0 bit 0, then BFI. But r0 is still the full + * double. We can use UBFX to isolate, but r0 is still needed for mant. + * + * Strategy: extract sign, shift, OR into r1. */ + /* Extract sign into r0 temporarily, then restore r0 from FP reg. */ + tcg_out_ubfm(s, TCG_TYPE_I64, r0, r0, 63, 63); /* r0 = sign (0 or 1) */ + /* r1 |= (sign << 15) */ + tcg_out_insn_3502S(s, I3510_ORR, TCG_TYPE_I32, r1, r1, r0, 15); + + /* Store sign_exp: STRH W(r1), [rd_ptr, #8] */ + tcg_out_ldst(s, I3312_STRH, r1, rd_ptr, 8, 1); + + /* --- Build mantissa (64-bit) in r0 --- */ + /* Reload double bits from FP reg */ + tcg_out32(s, I_FPMOV_X_D | (rn_fp & 0x1f) << 5 | (r0 & 0x1f)); + + /* Extract d_mant = bits[51:0] */ + tcg_out_ubfm(s, TCG_TYPE_I64, r0, r0, 0, 51); + + /* LSL r0, r0, #11 -> mant = d_mant << 11 */ + tcg_out_ubfm(s, TCG_TYPE_I64, r0, r0, 53, 52); + + /* Set integer bit (bit 63): ORR r0, r0, #(1<<63). + * 0x8000000000000000 is a valid ARM64 logical immediate. */ + tcg_out_logicali(s, I3404_ORRI, TCG_TYPE_I64, r0, r0, + UINT64_C(0x8000000000000000)); + + /* Store mantissa: STR X(r0), [rd_ptr, #0] */ + tcg_out_ldst(s, I3312_STRX, r0, rd_ptr, 0, 3); +} + +static void tcg_out_op(TCGContext *s, TCGOpcode opc, TCGType type, + const TCGArg args[TCG_MAX_OP_ARGS], + const int const_args[TCG_MAX_OP_ARGS]) +{ + TCGReg a0 = args[0], a1 = args[1], a2 = args[2]; + + switch (opc) { + /* --- Arithmetic (3-register) --- */ + case INDEX_op_add_f32: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FADD_S, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_add_f64: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FADD_D, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + case INDEX_op_sub_f32: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FSUB_S, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_sub_f64: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FSUB_D, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + case INDEX_op_mul_f32: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FMUL_S, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_mul_f64: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FMUL_D, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + case INDEX_op_div_f32: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FDIV_S, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_div_f64: +#if BISECT_FP_ARITH + tcg_out_fp2src(s, I_FP2_FDIV_D, a0, a1, a2); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + + /* --- Unary (2-register) --- */ + case INDEX_op_sqrt_f32: +#if BISECT_FP_ARITH + tcg_out_fp1src(s, I_FP1_FSQRT_S, a0, a1); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_sqrt_f64: +#if BISECT_FP_ARITH + tcg_out_fp1src(s, I_FP1_FSQRT_D, a0, a1); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + case INDEX_op_abs_f32: +#if BISECT_FP_ARITH + tcg_out_fp1src(s, I_FP1_FABS_S, a0, a1); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_abs_f64: +#if BISECT_FP_ARITH + tcg_out_fp1src(s, I_FP1_FABS_D, a0, a1); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + case INDEX_op_chs_f32: +#if BISECT_FP_ARITH + tcg_out_fp1src(s, I_FP1_FNEG_S, a0, a1); +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_chs_f64: +#if BISECT_FP_ARITH + tcg_out_fp1src(s, I_FP1_FNEG_D, a0, a1); +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + case INDEX_op_mov_f32: + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); + break; + case INDEX_op_mov_f64: + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); + break; + + /* --- Compare: FCMP + convert ARM64 NZCV to x86-style EFLAGS --- */ + case INDEX_op_com_f32: +#if BISECT_FP_COM + tcg_out_fp_com(s, I_FPCMP_FCMP_S, a0, a1, a2); +#else + tcg_out_movi(s, TCG_TYPE_I64, a0, 0); +#endif + break; + case INDEX_op_com_f64: +#if BISECT_FP_COM + tcg_out_fp_com(s, I_FPCMP_FCMP_D, a0, a1, a2); +#else + tcg_out_movi(s, TCG_TYPE_I64, a0, 0); +#endif + break; + + /* --- Conversions --- */ + case INDEX_op_cvt32i_f32: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_SCVTF_SW, a0, a1); +#else + tcg_out32(s, I_FPMOV_S_W | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt32i_f64: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_SCVTF_DW, a0, a1); +#else + tcg_out32(s, I_FPMOV_D_X | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt64i_f32: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_SCVTF_SX, a0, a1); +#else + tcg_out32(s, I_FPMOV_S_W | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt64i_f64: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_SCVTF_DX, a0, a1); +#else + tcg_out32(s, I_FPMOV_D_X | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt32f_i32: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_FCVTZS_WS, a0, a1); +#else + tcg_out32(s, I_FPMOV_W_S | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt32f_i64: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_FCVTZS_XS, a0, a1); +#else + tcg_out32(s, I_FPMOV_W_S | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt64f_i32: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_FCVTZS_WD, a0, a1); +#else + tcg_out32(s, I_FPMOV_W_S | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt64f_i64: +#if BISECT_FP_CVT + tcg_out_fpcvt(s, I_FPCVT_FCVTZS_XD, a0, a1); +#else + tcg_out32(s, I_FPMOV_X_D | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_cvt32f_f64: +#if BISECT_FP_CVT + tcg_out_fp1src(s, I_FP1_FCVT_DS, a0, a1); /* FCVT Dd, Sn (widen) */ +#else + tcg_out_fp1src(s, I_FP1_FMOV_S, a0, a1); +#endif + break; + case INDEX_op_cvt64f_f32: +#if BISECT_FP_CVT + tcg_out_fp1src(s, I_FP1_FCVT_SD, a0, a1); /* FCVT Sd, Dn (narrow) */ +#else + tcg_out_fp1src(s, I_FP1_FMOV_D, a0, a1); +#endif + break; + + /* --- Bit-copy between integer and FP registers --- */ + case INDEX_op_mov32i_f32: +#if BISECT_FP_MOVIF + tcg_out_fpcvt(s, I_FPMOV_S_W, a0, a1); +#else + tcg_out32(s, I_FPMOV_S_W | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_mov64i_f64: +#if BISECT_FP_MOVIF + tcg_out_fpcvt(s, I_FPMOV_D_X, a0, a1); +#else + tcg_out32(s, I_FPMOV_D_X | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_mov32f_i32: +#if BISECT_FP_MOVIF + tcg_out_fpcvt(s, I_FPMOV_W_S, a0, a1); +#else + tcg_out32(s, I_FPMOV_W_S | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_mov64f_i64: +#if BISECT_FP_MOVIF + tcg_out_fpcvt(s, I_FPMOV_X_D, a0, a1); +#else + tcg_out32(s, I_FPMOV_X_D | (a1 & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + + /* --- ld80f/st80f --- */ + case INDEX_op_ld80f_f32: +#if BISECT_FP_LD80F + tcg_out_ld80f(s, false, a0, a1); +#else + tcg_out32(s, I_FPMOV_S_W | (TCG_REG_XZR & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_ld80f_f64: +#if BISECT_FP_LD80F + tcg_out_ld80f(s, true, a0, a1); +#else + tcg_out32(s, I_FPMOV_D_X | (TCG_REG_XZR & 0x1f) << 5 | (a0 & 0x1f)); +#endif + break; + case INDEX_op_st80f_f32: +#if BISECT_FP_ST80F + tcg_out_st80f(s, false, a0, a1); +#else + /* NOP: don't store back */ + tcg_out32(s, 0xd503201f); +#endif + break; + case INDEX_op_st80f_f64: +#if BISECT_FP_ST80F + tcg_out_st80f(s, true, a0, a1); +#else + /* NOP: don't store back */ + tcg_out32(s, 0xd503201f); +#endif + break; + + /* --- sin/cos: not yet implemented, kept as helpers --- */ + case INDEX_op_sin_f32: + case INDEX_op_sin_f64: + case INDEX_op_cos_f32: + case INDEX_op_cos_f64: + g_assert_not_reached(); + break; + + /* --- flcr: write FP control register --- */ + /* Input a0 is MXCSR-formatted: rounding mode at bits [14:13]. + * ARM64 FPCR rounding mode at bits [23:22]. + * MXCSR: 00=RN, 01=RD(-inf), 10=RU(+inf), 11=RZ + * FPCR: 00=RN, 01=RU(+inf), 10=RD(-inf), 11=RZ + * Need to swap bits 0 and 1 of the 2-bit RC field. */ + case INDEX_op_flcr: +#if BISECT_FP_FLCR + { + TCGReg t0 = TCG_REG_TMP0; + TCGReg t1 = TCG_REG_TMP1; + + /* Extract MXCSR RC bits [14:13] */ + tcg_out_ubfm(s, TCG_TYPE_I32, t0, a0, 13, 14); + + /* Swap bit 0 and bit 1: new = (old[1]) | (old[0] << 1) */ + tcg_out_ubfm(s, TCG_TYPE_I32, t1, t0, 0, 0); /* t1 = bit 0 */ + tcg_out_ubfm(s, TCG_TYPE_I32, t0, t0, 1, 1); /* t0 = bit 1 */ + tcg_out_insn_3502S(s, I3510_ORR, TCG_TYPE_I32, + t0, t0, t1, 1); /* t0 = bit1 | (bit0 << 1) */ + + /* Shift to FPCR position [23:22] */ + tcg_out_ubfm(s, TCG_TYPE_I32, t0, t0, 32 - 22, 31 - 22); + + /* MSR FPCR, X(t0) */ + tcg_out32(s, I_MSR_FPCR | (t0 & 0x1f)); + } +#else + /* NOP: leave FPCR at default (round-to-nearest) */ + tcg_out32(s, 0xd503201f); +#endif + break; + + default: + g_assert_not_reached(); + } } static void tcg_out_vec_op(TCGContext *s, TCGOpcode opc, @@ -3388,6 +4126,74 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags) case INDEX_op_aa64_sli_vec: return C_O1_I2(w, 0, w); + /* Scalar FP: arithmetic (3-register) */ + case INDEX_op_add_f32: + case INDEX_op_add_f64: + case INDEX_op_sub_f32: + case INDEX_op_sub_f64: + case INDEX_op_mul_f32: + case INDEX_op_mul_f64: + case INDEX_op_div_f32: + case INDEX_op_div_f64: + return C_O1_I2(w, w, w); + + /* Scalar FP: unary (2-register) */ + case INDEX_op_sqrt_f32: + case INDEX_op_sqrt_f64: + case INDEX_op_abs_f32: + case INDEX_op_abs_f64: + case INDEX_op_chs_f32: + case INDEX_op_chs_f64: + case INDEX_op_mov_f32: + case INDEX_op_mov_f64: + case INDEX_op_cvt32f_f64: + case INDEX_op_cvt64f_f32: + return C_O1_I1(w, w); + + /* Scalar FP: compare -> result in GPR */ + case INDEX_op_com_f32: + case INDEX_op_com_f64: + return C_O1_I2(r, w, w); + + /* Int-to-FP: GPR input, FP output */ + case INDEX_op_cvt32i_f32: + case INDEX_op_cvt32i_f64: + case INDEX_op_cvt64i_f32: + case INDEX_op_cvt64i_f64: + case INDEX_op_mov32i_f32: + case INDEX_op_mov64i_f64: + return C_O1_I1(w, r); + + /* FP-to-Int: FP input, GPR output */ + case INDEX_op_cvt32f_i32: + case INDEX_op_cvt32f_i64: + case INDEX_op_cvt64f_i32: + case INDEX_op_cvt64f_i64: + case INDEX_op_mov32f_i32: + case INDEX_op_mov64f_i64: + return C_O1_I1(r, w); + + /* ld80f: FP output, GPR input (memory pointer) */ + case INDEX_op_ld80f_f32: + case INDEX_op_ld80f_f64: + return C_O1_I1(w, r); + + /* st80f: FP input, GPR input (memory pointer) */ + case INDEX_op_st80f_f32: + case INDEX_op_st80f_f64: + return C_O0_I2(w, r); + + /* sin/cos: FP input, FP output */ + case INDEX_op_sin_f32: + case INDEX_op_sin_f64: + case INDEX_op_cos_f32: + case INDEX_op_cos_f64: + return C_O1_I1(w, w); + + /* flcr: GPR input only */ + case INDEX_op_flcr: + return C_O0_I1(r); + default: return C_NotImplemented; } @@ -3399,6 +4205,8 @@ static void tcg_target_init(TCGContext *s) tcg_target_available_regs[TCG_TYPE_I64] = 0xffffffffu; tcg_target_available_regs[TCG_TYPE_V64] = 0xffffffff00000000ull; tcg_target_available_regs[TCG_TYPE_V128] = 0xffffffff00000000ull; + tcg_target_available_regs[TCG_TYPE_F32] = ALL_VECTOR_REGS; + tcg_target_available_regs[TCG_TYPE_F64] = ALL_VECTOR_REGS; tcg_target_call_clobber_regs = -1ull; tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_X19); @@ -3485,6 +4293,19 @@ static void tcg_target_qemu_prologue(TCGContext *s) tcg_regset_set_reg(s->reserved_regs, TCG_REG_GUEST_BASE); } +#ifdef XBOX + /* + * X27 = &xbox_ram_fp (struct address). + * The fast path dereferences X27 at runtime to check active flag, + * then loads host_base. Avoids init-order problems since the address + * is stable even before xbox_memory_init() runs. + */ + tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_XBOX_RAM_BASE, + (uintptr_t)&xbox_ram_fp); + tcg_regset_set_reg(s->reserved_regs, TCG_REG_XBOX_RAM_BASE); + tcg_regset_set_reg(s->reserved_regs, TCG_REG_XBOX_HOST_BASE); +#endif + tcg_out_mov(s, TCG_TYPE_PTR, TCG_AREG0, tcg_target_call_iarg_regs[0]); tcg_out_insn(s, 3207, BR, tcg_target_call_iarg_regs[1]); @@ -3519,6 +4340,45 @@ static void tcg_target_qemu_prologue(TCGContext *s) static void tcg_out_tb_start(TCGContext *s) { tcg_out_bti(s, BTI_J); + +#ifdef XBOX + if (xbox_ram_size) { + /* + * Preamble: check active/cb_count once per TB and cache + * host_base in X26. X27 (&xbox_ram_fp) stays unchanged + * so goto_tb chaining works. X26 = host_base when the fast + * path is usable, 0 otherwise. + */ + tcg_insn_unit *label_no_fp, *label_no_fp2, *label_end; + + tcg_out_ld(s, TCG_TYPE_I32, TCG_REG_TMP0, + TCG_REG_XBOX_RAM_BASE, + offsetof(XboxRamFPState, active)); + label_no_fp = s->code_ptr; + tcg_out_insn(s, 3201, CBZ, 0, TCG_REG_TMP0, 0); + + tcg_out_ld(s, TCG_TYPE_I32, TCG_REG_TMP0, + TCG_REG_XBOX_RAM_BASE, + offsetof(XboxRamFPState, cb_count)); + label_no_fp2 = s->code_ptr; + tcg_out_insn(s, 3201, CBNZ, 0, TCG_REG_TMP0, 0); + + tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_XBOX_HOST_BASE, + TCG_REG_XBOX_RAM_BASE, + offsetof(XboxRamFPState, host_base)); + + label_end = s->code_ptr; + tcg_out_insn(s, 3206, B, 0); + + /* .no_fp: X26 = 0 signals per-load fast path is disabled */ + reloc_pc19(label_no_fp, tcg_splitwx_to_rx(s->code_ptr)); + reloc_pc19(label_no_fp2, tcg_splitwx_to_rx(s->code_ptr)); + tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_XBOX_HOST_BASE, 0); + + /* .end_preamble: */ + reloc_pc26(label_end, tcg_splitwx_to_rx(s->code_ptr)); + } +#endif } static void tcg_out_nop_fill(tcg_insn_unit *p, int count) diff --git a/tcg/optimize.c b/tcg/optimize.c index f69702b26e..7661250574 100644 --- a/tcg/optimize.c +++ b/tcg/optimize.c @@ -30,6 +30,10 @@ #include "tcg-internal.h" #include "tcg-has.h" +#ifndef XEMU_OPT_TCG_PEEPHOLE +#define XEMU_OPT_TCG_PEEPHOLE 1 +#endif + typedef struct MemCopyInfo { IntervalTreeNode itree; @@ -1580,6 +1584,43 @@ static bool fold_bitsel_vec(OptContext *ctx, TCGOp *op) static bool fold_brcond(OptContext *ctx, TCGOp *op) { +#if XEMU_OPT_TCG_PEEPHOLE + /* + * Peephole: fuse setcond + brcond. + * + * setcond tmp, a, b, cond brcond tmp, 0, NE, label + * becomes: + * brcond a, b, cond, label + * + * setcond tmp, a, b, cond brcond tmp, 0, EQ, label + * becomes: + * brcond a, b, inv(cond), label + * + * This eliminates the intermediate boolean materialization that + * the x86 frontend generates for conditional jumps. + */ + TCGOp *prev = QTAILQ_PREV(op, link); + if (prev && prev->opc == INDEX_op_setcond + && arg_is_const_val(op->args[1], 0) + && op->args[0] == prev->args[0] + && arg_temp(prev->args[0])->kind < TEMP_GLOBAL) { + TCGCond brcond = op->args[2]; + TCGCond setcond = prev->args[3]; + + if (brcond == TCG_COND_NE) { + op->args[0] = prev->args[1]; + op->args[1] = prev->args[2]; + op->args[2] = setcond; + tcg_op_remove(ctx->tcg, prev); + } else if (brcond == TCG_COND_EQ) { + op->args[0] = prev->args[1]; + op->args[1] = prev->args[2]; + op->args[2] = tcg_invert_cond(setcond); + tcg_op_remove(ctx->tcg, prev); + } + } +#endif /* XEMU_OPT_TCG_PEEPHOLE */ + int i = do_constant_folding_cond1(ctx, op, NO_DEST, &op->args[0], &op->args[1], &op->args[2]); if (i == 0) { diff --git a/tcg/region.c b/tcg/region.c index 2181267e48..823c445cc1 100644 --- a/tcg/region.c +++ b/tcg/region.c @@ -416,6 +416,11 @@ void tcg_region_reset_all(void) for (i = 0; i < n_ctxs; i++) { TCGContext *s = qatomic_read(&tcg_ctxs[i]); tcg_region_initial_alloc__locked(s); +#ifdef XBOX + if (s->hot_arena_start) { + s->hot_arena_ptr = s->hot_arena_start; + } +#endif } qemu_mutex_unlock(®ion.lock); @@ -866,6 +871,18 @@ void tcg_region_prologue_set(TCGContext *s) g_assert(region.start_aligned == s->code_gen_buffer); region.after_prologue = s->code_ptr; +#ifdef XBOX + /* + * Carve out a hot arena for tier-1 TBs right after the prologue. + * Sized to match a typical ARM64 L1I cache (64 KB). + */ +#define HOT_ARENA_SIZE (64 * 1024) + s->hot_arena_start = region.after_prologue; + s->hot_arena_end = s->hot_arena_start + HOT_ARENA_SIZE; + s->hot_arena_ptr = s->hot_arena_start; + region.after_prologue = s->hot_arena_end; +#endif + /* Recompute boundaries of the first region. */ tcg_region_assign(s, 0); diff --git a/tcg/tcg-internal.h b/tcg/tcg-internal.h index d6a12afe06..08a9e24a22 100644 --- a/tcg/tcg-internal.h +++ b/tcg/tcg-internal.h @@ -111,4 +111,15 @@ TCGOp *tcg_op_insert_before(TCGContext *s, TCGOp *op, TCGOp *tcg_op_insert_after(TCGContext *s, TCGOp *op, TCGOpcode, TCGType, unsigned nargs); +/* Tier 1 optimization passes (tier1-opt.c) */ +#ifdef XBOX +void tier1_dead_flag_elimination(TCGContext *s); +uint8_t tier1_compute_cc_defines_first(TCGContext *s); +void tier1_cross_tb_dead_flag_elimination(TCGContext *s); +#else +static inline void tier1_dead_flag_elimination(TCGContext *s) {} +static inline uint8_t tier1_compute_cc_defines_first(TCGContext *s) { return 0; } +static inline void tier1_cross_tb_dead_flag_elimination(TCGContext *s) {} +#endif + #endif /* TCG_INTERNAL_H */ diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c index a7b333baab..a992fba2a1 100644 --- a/tcg/tcg-op.c +++ b/tcg/tcg-op.c @@ -300,9 +300,11 @@ void tcg_gen_mb(TCGBar mb_type) bool parallel = tcg_ctx->gen_tb->cflags & CF_PARALLEL; #elif defined(XBOX) /* - * Xbox is single-CPU and never sets CF_PARALLEL. Its device threads - * access guest memory via the memory API or under the BQL, so we can - * avoid emitting per-instruction barriers on this target. + * Xbox has a single CPU and never sets CF_PARALLEL. All I/O device + * threads (NV2A, APU, etc.) access guest memory through the QEMU + * memory API or under BQL, which provides its own ordering. + * Eliding per-instruction DMB barriers saves ~30-50 cycles per + * guest memory operation on ARM64. */ bool parallel = tcg_ctx->gen_tb->cflags & CF_PARALLEL; #else diff --git a/tcg/tcg.c b/tcg/tcg.c index 7967bbcafd..b2661dd3f1 100644 --- a/tcg/tcg.c +++ b/tcg/tcg.c @@ -250,6 +250,14 @@ bool tcg_use_softmmu; TCGContext tcg_init_ctx; __thread TCGContext *tcg_ctx; +#ifdef XBOX +XboxRamFPState xbox_ram_fp; +uint64_t xbox_ram_size; +volatile int32_t *xbox_ram_fp_cb_count_ptr = &xbox_ram_fp.cb_count; +volatile int32_t *xbox_ram_fp_active_ptr = &xbox_ram_fp.active; +uintptr_t *xbox_ram_fp_vram_base_ptr = &xbox_ram_fp.vram_pci_base; +#endif + TCGContext **tcg_ctxs; unsigned int tcg_cur_ctxs; unsigned int tcg_max_ctxs; @@ -1904,6 +1912,27 @@ TranslationBlock *tcg_tb_alloc(TCGContext *s) return tb; } +#ifdef XBOX +TranslationBlock *tcg_tb_alloc_hot(TCGContext *s) +{ + if (!s->hot_arena_start) { + return NULL; + } + uintptr_t align = qemu_icache_linesize; + TranslationBlock *tb; + void *next; + + tb = (void *)ROUND_UP((uintptr_t)s->hot_arena_ptr, align); + next = (void *)ROUND_UP((uintptr_t)(tb + 1), align); + + if (next > s->hot_arena_end) { + return NULL; + } + s->hot_arena_ptr = next; + return tb; +} +#endif + void tcg_prologue_init(void) { TCGContext *s = tcg_ctx; @@ -2008,6 +2037,9 @@ void tcg_func_start(TCGContext *s) QTAILQ_INIT(&s->free_ops); s->emit_before_op = NULL; QSIMPLEQ_INIT(&s->labels); +#ifdef XBOX + s->superblock_append = false; +#endif tcg_debug_assert(s->addr_type <= TCG_TYPE_REG); } @@ -6999,6 +7031,15 @@ int tcg_gen_code(TCGContext *s, TranslationBlock *tb, uint64_t pc_start) tcg_optimize(s); +#ifdef XBOX + s->gen_tb->cc_defines_first = tier1_compute_cc_defines_first(s); + + if (tb_cflags(s->gen_tb) & CF_TIER1) { + tier1_dead_flag_elimination(s); + tier1_cross_tb_dead_flag_elimination(s); + } +#endif + reachable_code_pass(s); liveness_pass_0(s); liveness_pass_1(s); @@ -7039,6 +7080,11 @@ int tcg_gen_code(TCGContext *s, TranslationBlock *tb, uint64_t pc_start) tb->jmp_insn_offset[0] = TB_JMP_OFFSET_INVALID; tb->jmp_insn_offset[1] = TB_JMP_OFFSET_INVALID; + /* tier1_global_register_pinning and tier1_instruction_scheduling + * removed: register preferences are ignored for short-lived values, + * and O(n^2) scheduling on 5-15 op blocks adds overhead with no + * measurable benefit. Dead flag elimination (above) is retained. */ + tcg_reg_alloc_start(s); /* diff --git a/tcg/tier1-opt.c b/tcg/tier1-opt.c new file mode 100644 index 0000000000..7991b623ff --- /dev/null +++ b/tcg/tier1-opt.c @@ -0,0 +1,378 @@ +/* + * Tier 1 Optimizations for the Two-Tier TCG JIT + * + * Dead flag elimination: backward dataflow analysis over CC state + * globals (cc_op, cc_dst, cc_src, cc_src2) to remove writes whose + * results are overwritten before being read. + * + * This pass runs only for TBs compiled with CF_TIER1 set, after + * tcg_optimize() and before the liveness passes. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "tcg/tcg.h" +#include "tcg/tcg-op-common.h" +#include "tcg-internal.h" +#include "exec/translation-block.h" + +#ifdef XBOX + +/* + * We track liveness of four CC-related globals from the x86 frontend: + * cc_op, cc_dst, cc_src, cc_src2 + * + * These are identified at pass start by scanning TCGContext->temps[] + * for TEMP_GLOBAL entries whose name matches. + */ + +#define CC_GLOBAL_OP 0 +#define CC_GLOBAL_DST 1 +#define CC_GLOBAL_SRC 2 +#define CC_GLOBAL_SRC2 3 +#define CC_GLOBAL_COUNT 4 + +typedef struct Tier1DFEContext { + TCGContext *tcg; + TCGTemp *cc_temps[CC_GLOBAL_COUNT]; + int num_eliminated; +} Tier1DFEContext; + +/* + * Find the CC global temps by matching names. + * Returns true if all four were found. + */ +static bool find_cc_globals(Tier1DFEContext *ctx) +{ + TCGContext *s = ctx->tcg; + int found = 0; + + memset(ctx->cc_temps, 0, sizeof(ctx->cc_temps)); + + for (int i = 0; i < s->nb_globals; i++) { + TCGTemp *ts = &s->temps[i]; + if (!ts->name) { + continue; + } + if (strcmp(ts->name, "cc_op") == 0) { + ctx->cc_temps[CC_GLOBAL_OP] = ts; + found++; + } else if (strcmp(ts->name, "cc_dst") == 0) { + ctx->cc_temps[CC_GLOBAL_DST] = ts; + found++; + } else if (strcmp(ts->name, "cc_src") == 0) { + ctx->cc_temps[CC_GLOBAL_SRC] = ts; + found++; + } else if (strcmp(ts->name, "cc_src2") == 0) { + ctx->cc_temps[CC_GLOBAL_SRC2] = ts; + found++; + } + } + + return found == CC_GLOBAL_COUNT; +} + +/* + * Check if a TCGTemp is one of the tracked CC globals. + * Returns the index (0-3) or -1 if not. + */ +static inline int cc_global_index(const Tier1DFEContext *ctx, TCGTemp *ts) +{ + for (int i = 0; i < CC_GLOBAL_COUNT; i++) { + if (ts == ctx->cc_temps[i]) { + return i; + } + } + return -1; +} + +/* + * Perform dead flag elimination on the TCG IR. + * + * Walk backward through the ops. Track a liveness bitmask for the + * four CC globals. At TB exit points, conservatively mark all CC + * globals live. When we encounter a write to a dead CC global + * (in a side-effect-free op), remove the op. When we encounter a + * read of a CC global, mark it live. When we encounter a write + * that satisfies a live read, mark the global dead. + */ +void tier1_dead_flag_elimination(TCGContext *s) +{ + Tier1DFEContext ctx = { .tcg = s, .num_eliminated = 0 }; + TCGOp *op, *op_prev; + + if (!find_cc_globals(&ctx)) { + return; + } + + /* + * Liveness bitmask: bit i is set if cc_temps[i] is live. + * Start with all live (conservative for TB exit). + */ + uint32_t cc_live = (1u << CC_GLOBAL_COUNT) - 1; + + QTAILQ_FOREACH_REVERSE_SAFE(op, &s->ops, link, op_prev) { + TCGOpcode opc = op->opc; + const TCGOpDef *def; + + /* + * At basic block boundaries (branches, exits, labels), + * conservatively assume all CC globals are live. + */ + if (opc == INDEX_op_set_label) { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + continue; + } + + def = &tcg_op_defs[opc]; + + if (def->flags & (TCG_OPF_BB_EXIT | TCG_OPF_BB_END | + TCG_OPF_COND_BRANCH)) { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + } + + /* + * For call ops, conservatively assume all CC globals are + * both read and written (the helper might access cpu state). + */ + if (opc == INDEX_op_call) { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + continue; + } + + /* + * Check if this op writes to any CC globals. + * If the written CC global is dead and the op has no side effects, + * we can remove the op (if it ONLY writes CC globals or dead temps). + */ + int nb_oargs = def->nb_oargs; + int nb_iargs = def->nb_iargs; + bool writes_cc = false; + bool all_outputs_dead_cc = true; + uint32_t written_cc_mask = 0; + + for (int i = 0; i < nb_oargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + writes_cc = true; + written_cc_mask |= (1u << idx); + if (cc_live & (1u << idx)) { + all_outputs_dead_cc = false; + } + } else { + all_outputs_dead_cc = false; + } + } + + /* + * If this op only writes to dead CC globals and has no + * side effects, remove it. + */ + if (writes_cc && all_outputs_dead_cc && nb_oargs > 0 && + !(def->flags & TCG_OPF_SIDE_EFFECTS)) { + tcg_op_remove(s, op); + ctx.num_eliminated++; + continue; + } + + /* + * Process writes: a write to a live CC global satisfies the + * pending read, so mark it dead going backward. + */ + if (writes_cc) { + for (int i = 0; i < nb_oargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + cc_live &= ~(1u << idx); + } + } + } + + /* + * Process reads: a read of a CC global makes it live. + */ + for (int i = nb_oargs; i < nb_oargs + nb_iargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + cc_live |= (1u << idx); + } + } + } +} + +/* + * Compute cc_defines_first: forward scan of IR to find which CC globals + * are DEFINED before any USE. Stored on the TB for cross-TB DFE. + * + * Returns a bitmask where bit i is set if cc_temps[i] is written + * before being read in the TB's IR. + */ +uint8_t tier1_compute_cc_defines_first(TCGContext *s) +{ + Tier1DFEContext ctx = { .tcg = s }; + TCGOp *op; + uint32_t defined = 0; + uint32_t used = 0; + + if (!find_cc_globals(&ctx)) { + return 0; + } + + QTAILQ_FOREACH(op, &s->ops, link) { + TCGOpcode opc = op->opc; + const TCGOpDef *def; + + if (opc == INDEX_op_insn_start) { + continue; + } + + /* At calls or branches, stop -- subsequent code may not execute. */ + if (opc == INDEX_op_call || opc == INDEX_op_set_label) { + break; + } + + def = &tcg_op_defs[opc]; + if (def->flags & (TCG_OPF_BB_EXIT | TCG_OPF_BB_END | + TCG_OPF_COND_BRANCH)) { + break; + } + + int nb_oargs = def->nb_oargs; + int nb_iargs = def->nb_iargs; + + /* Check reads first (order matters: read before write in same op). */ + for (int i = nb_oargs; i < nb_oargs + nb_iargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + used |= (1u << idx); + } + } + + /* Then check writes. */ + for (int i = 0; i < nb_oargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0 && !(used & (1u << idx))) { + defined |= (1u << idx); + } + } + } + + return (uint8_t)(defined & 0xf); +} + +/* + * Cross-TB dead flag elimination for tier1 TBs. + * + * For goto_tb exits with a known successor TB, check the successor's + * cc_defines_first mask (pre-populated in s->succ_cc_defines[] by the + * caller in translate-all.c). If the successor defines a CC global + * before using it, the predecessor's store of that global at exit is dead. + * + * This extends the existing intra-TB DFE by reducing the conservative + * "all live" assumption at exit points. + */ +void tier1_cross_tb_dead_flag_elimination(TCGContext *s) +{ + Tier1DFEContext ctx = { .tcg = s, .num_eliminated = 0 }; + TCGOp *op, *op_prev; + + if (!find_cc_globals(&ctx)) { + return; + } + + if (s->succ_cc_defines[0] == 0 && s->succ_cc_defines[1] == 0) { + return; + } + + uint32_t cc_live = (1u << CC_GLOBAL_COUNT) - 1; + + QTAILQ_FOREACH_REVERSE_SAFE(op, &s->ops, link, op_prev) { + TCGOpcode opc = op->opc; + const TCGOpDef *def; + + if (opc == INDEX_op_set_label) { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + continue; + } + + def = &tcg_op_defs[opc]; + + if (def->flags & (TCG_OPF_BB_EXIT | TCG_OPF_BB_END | + TCG_OPF_COND_BRANCH)) { + /* + * For goto_tb, check if we know the successor. + * The goto_tb index (0 or 1) is in args[0]. + */ + if (opc == INDEX_op_goto_tb) { + int idx = op->args[0]; + if (idx < 2 && s->succ_cc_defines[idx]) { + cc_live = ((1u << CC_GLOBAL_COUNT) - 1) + & ~s->succ_cc_defines[idx]; + } else { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + } + } else { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + } + continue; + } + + if (opc == INDEX_op_call) { + cc_live = (1u << CC_GLOBAL_COUNT) - 1; + continue; + } + + int nb_oargs = def->nb_oargs; + int nb_iargs = def->nb_iargs; + bool writes_cc = false; + bool all_outputs_dead_cc = true; + uint32_t written_cc_mask = 0; + + for (int i = 0; i < nb_oargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + writes_cc = true; + written_cc_mask |= (1u << idx); + if (cc_live & (1u << idx)) { + all_outputs_dead_cc = false; + } + } else { + all_outputs_dead_cc = false; + } + } + + if (writes_cc && all_outputs_dead_cc && nb_oargs > 0 && + !(def->flags & TCG_OPF_SIDE_EFFECTS)) { + tcg_op_remove(s, op); + ctx.num_eliminated++; + continue; + } + + if (writes_cc) { + for (int i = 0; i < nb_oargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + cc_live &= ~(1u << idx); + } + } + } + + for (int i = nb_oargs; i < nb_oargs + nb_iargs; i++) { + TCGTemp *ts = arg_temp(op->args[i]); + int idx = cc_global_index(&ctx, ts); + if (idx >= 0) { + cc_live |= (1u << idx); + } + } + } +} + +#endif /* XBOX */ diff --git a/tests/lcitool/libvirt-ci b/tests/lcitool/libvirt-ci index caed407a0f..5176e136ab 160000 --- a/tests/lcitool/libvirt-ci +++ b/tests/lcitool/libvirt-ci @@ -1 +1 @@ -Subproject commit caed407a0f1dfe3a3293a41c378c382deefbac36 +Subproject commit 5176e136ab11e275eb9f57c3d5c80e77af6507cb diff --git a/ui/xemu.c b/ui/xemu.c index 9aa9c195df..46cd4c6ecf 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -63,9 +63,6 @@ #ifdef __ANDROID__ #include -#ifdef __aarch64__ -#include -#endif #endif #ifdef _WIN32 #include "nvapi.h" @@ -103,10 +100,6 @@ void xb_surface_gl_create_texture(DisplaySurface *surface); void xb_surface_gl_update_texture(DisplaySurface *surface, int x, int y, int w, int h); void xb_surface_gl_destroy_texture(DisplaySurface *surface); -#ifdef __ANDROID__ -static int64_t android_monotonic_time_ns(void); -static void android_sleep_until_ns(int64_t deadline_ns); -#endif static void sleep_ns(int64_t ns); static int sdl2_num_outputs; @@ -139,38 +132,18 @@ static bool g_android_gl_bgra_supported = true; static bool g_android_force_finish_before_swap = false; static bool g_android_paused = false; static bool g_android_should_quit = false; +static volatile bool g_android_vm_pause_requested = false; +static volatile bool g_android_vm_resume_requested = false; static uint64_t g_android_frame_counter = 0; static int g_android_target_fps = 60; static int64_t g_android_frame_interval_ns = 16666666; static int g_android_display_mode = 0; /* 0=stretch, 1=4:3, 2=16:9 */ -static int g_android_render_logs_enabled = -1; static bool sdl2_is_render_thread(void) { return sdl_render_thread_id != 0 && SDL_ThreadID() == sdl_render_thread_id; } -static bool android_render_logs_enabled(void) -{ - if (g_android_render_logs_enabled < 0) { - const char *value = SDL_getenv("XEMU_ANDROID_RENDER_LOGS"); - g_android_render_logs_enabled = - (value && value[0] != '\0' && value[0] != '0') ? 1 : 0; - if (g_android_render_logs_enabled) { - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "android: verbose render logging enabled"); - } - } - return g_android_render_logs_enabled != 0; -} - -static bool android_should_log_render_periodic(uint64_t period) -{ - return android_render_logs_enabled() && - period != 0 && - (g_android_frame_counter % period) == 0; -} - static bool sdl2_gl_has_extension(const char *ext_list, const char *ext) { if (!ext_list || !ext || !ext[0]) { @@ -191,10 +164,6 @@ static bool sdl2_gl_has_extension(const char *ext_list, const char *ext) static void android_log_gl_error(const char *stage) { - if (!android_render_logs_enabled()) { - return; - } - GLenum err; bool logged = false; while ((err = glGetError()) != GL_NO_ERROR) { @@ -215,6 +184,22 @@ int xemu_android_get_display_mode_setting(void) return g_android_display_mode; } +void xemu_android_pause_emulation(void) +{ + g_android_vm_pause_requested = true; +} + +void xemu_android_resume_emulation(void) +{ + g_android_vm_resume_requested = true; +} + +void xemu_android_request_exit(void) +{ + g_android_should_quit = true; + qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); +} + static void xemu_android_refresh_frame_limit_from_env(void) { int fps = 60; @@ -227,7 +212,7 @@ static void xemu_android_refresh_frame_limit_from_env(void) } } - if (fps != 60) { + if (fps <= 0 || fps > 240) { fps = 60; } @@ -294,7 +279,7 @@ static void sdl2_gl_render_texture(struct sdl2_console *scon, } #ifdef __ANDROID__ - if (android_should_log_render_periodic(60)) { + if ((g_android_frame_counter % 60) == 0) { __android_log_print(ANDROID_LOG_INFO, "xemu-android", "present drawable=%dx%d window=%dx%d viewport=%d,%d %dx%d tex=%u flip=%d mode=%d", w, h, ww, wh, vx, vy, vw, vh, @@ -829,16 +814,12 @@ void sdl2_poll_events(struct sdl2_console *scon) "android: app terminating, flushed"); break; case SDL_APP_WILLENTERBACKGROUND: - bdrv_flush_all(); g_android_paused = true; + bdrv_flush_all(); __android_log_print(ANDROID_LOG_INFO, "xemu-android", "android: app background, flushed"); break; case SDL_APP_DIDENTERBACKGROUND: - g_android_paused = true; - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "android: app background"); - break; case SDL_APP_WILLENTERFOREGROUND: case SDL_APP_DIDENTERFOREGROUND: g_android_paused = false; @@ -1395,7 +1376,7 @@ void xemu_android_display_loop(void) } #ifdef __ANDROID__ xemu_android_refresh_frame_limit_from_env(); - SDL_GL_SetSwapInterval(0); + SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0); xemu_hud_init(m_window, m_context); #endif tcg_register_init_ctx(); @@ -1410,15 +1391,35 @@ void xemu_android_display_loop(void) if (g_android_should_quit || qemu_shutdown_requested_get() != SHUTDOWN_CAUSE_NONE) { break; } + if (g_android_vm_pause_requested) { + qemu_mutex_lock_main_loop(); + bql_lock(); + if (runstate_is_running()) { + vm_stop(RUN_STATE_PAUSED); + } + g_android_vm_pause_requested = false; + bql_unlock(); + qemu_mutex_unlock_main_loop(); + } if (g_android_paused || sdl2_console[0].hidden) { qemu_mutex_lock_main_loop(); bql_lock(); sdl2_poll_events(&sdl2_console[0]); bql_unlock(); qemu_mutex_unlock_main_loop(); - SDL_Delay(100); + SDL_Delay(16); continue; } + if (g_android_vm_resume_requested) { + qemu_mutex_lock_main_loop(); + bql_lock(); + if (!runstate_is_running()) { + vm_start(); + } + g_android_vm_resume_requested = false; + bql_unlock(); + qemu_mutex_unlock_main_loop(); + } sdl2_gl_refresh(&sdl2_console[0].dcl); #ifdef __ANDROID__ if (!g_android_paused && SDL_GL_GetCurrentContext() != NULL) { @@ -1469,49 +1470,28 @@ void xb_surface_gl_create_texture(DisplaySurface *surface) uint8_t *converted = NULL; bool use_row_length = true; if (upload_format == GL_BGRA_EXT) { - if (g_android_gl_bgra_supported) { - /* GL_EXT_texture_format_BGRA8888: upload native BGRA with no CPU - * work. The spec requires internalformat == GL_BGRA_EXT too. */ - internal_format = GL_BGRA_EXT; - /* upload_format stays GL_BGRA_EXT, use_row_length stays true */ - } else { - /* Fallback: swizzle BGRA→RGBA on CPU before upload */ - const int width = surface_width(surface); - const int height = surface_height(surface); - const int stride = surface_stride(surface); - const uint8_t *src = (const uint8_t *)surface_data(surface); - converted = g_malloc((size_t)width * height * 4); - for (int y = 0; y < height; ++y) { - const uint8_t *row = src + (size_t)y * stride; - uint8_t *dst = converted + (size_t)y * width * 4; -#ifdef __aarch64__ - /* vqtbl1q_u8: 16-byte table lookup, processes 4 pixels/cycle */ - static const uint8_t perm_arr[16] = - {2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15}; - uint8x16_t vperm = vld1q_u8(perm_arr); - int px = width; - while (px >= 4) { - vst1q_u8(dst, vqtbl1q_u8(vld1q_u8(row), vperm)); - row += 16; dst += 16; px -= 4; - } - while (px-- > 0) { - dst[0] = row[2]; dst[1] = row[1]; - dst[2] = row[0]; dst[3] = row[3]; - row += 4; dst += 4; - } -#else - for (int x = 0; x < width; ++x) { - dst[x * 4 + 0] = row[x * 4 + 2]; - dst[x * 4 + 1] = row[x * 4 + 1]; - dst[x * 4 + 2] = row[x * 4 + 0]; - dst[x * 4 + 3] = row[x * 4 + 3]; - } -#endif + const int width = surface_width(surface); + const int height = surface_height(surface); + const int stride = surface_stride(surface); + const uint8_t *src = (const uint8_t *)surface_data(surface); + converted = g_malloc((size_t)width * height * 4); + for (int y = 0; y < height; ++y) { + const uint8_t *row = src + (size_t)y * stride; + uint8_t *dst = converted + (size_t)y * width * 4; + for (int x = 0; x < width; ++x) { + const uint8_t b = row[x * 4 + 0]; + const uint8_t g = row[x * 4 + 1]; + const uint8_t r = row[x * 4 + 2]; + const uint8_t a = row[x * 4 + 3]; + dst[x * 4 + 0] = r; + dst[x * 4 + 1] = g; + dst[x * 4 + 2] = b; + dst[x * 4 + 3] = a; } - upload_format = GL_RGBA; - pixels = converted; - use_row_length = false; } + upload_format = GL_RGBA; + pixels = converted; + use_row_length = false; } if (use_row_length) { #endif @@ -1614,14 +1594,13 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) return; } if (g_android_paused || scon->hidden) { - if (android_should_log_render_periodic(120)) { + if ((g_android_frame_counter++ % 120) == 0) { __android_log_print(ANDROID_LOG_INFO, "xemu-android", "refresh paused: hidden=%d paused=%d runstate=%d", scon->hidden ? 1 : 0, g_android_paused ? 1 : 0, (int)runstate_get()); } - g_android_frame_counter++; qemu_mutex_lock_main_loop(); bql_lock(); sdl2_poll_events(scon); @@ -1666,7 +1645,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) GLuint tex = nv2a_get_framebuffer_surface(); #ifdef __ANDROID__ if (tex != 0 && glIsTexture(tex) == GL_FALSE) { - if (android_should_log_render_periodic(120)) { + if ((g_android_frame_counter % 120) == 0) { __android_log_print(ANDROID_LOG_WARN, "xemu-android", "refresh: nv2a tex %u not valid in display context", (unsigned)tex); @@ -1676,7 +1655,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) #endif #ifdef __ANDROID__ android_log_gl_error("refresh-get-fb"); - if (android_should_log_render_periodic(120)) { + if ((g_android_frame_counter % 120) == 0) { __android_log_print(ANDROID_LOG_INFO, "xemu-android", "refresh frame=%llu tex=%u flip=%d surface=%p size=%dx%d runstate=%d", (unsigned long long)g_android_frame_counter, @@ -1703,7 +1682,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) tex = scon->surface->texture; flip_required = true; #ifdef __ANDROID__ - if (android_should_log_render_periodic(120)) { + if ((g_android_frame_counter % 120) == 0) { __android_log_print(ANDROID_LOG_INFO, "xemu-android", "refresh no nv2a fb, using surface texture=%u", (unsigned)tex); @@ -1772,41 +1751,9 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) */ static int64_t last_update = 0; #ifdef __ANDROID__ - const int64_t frame_interval = g_android_frame_interval_ns; + int64_t deadline = last_update + g_android_frame_interval_ns; #else - const int64_t frame_interval = 16666666; -#endif - if (last_update == 0) { -#ifdef __ANDROID__ - last_update = android_monotonic_time_ns(); -#else - last_update = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); -#endif - } - int64_t deadline = last_update + frame_interval; - -#ifdef __ANDROID__ - /* Sleep directly to the deadline — no spin threshold. Let the kernel - * wake us as close to the target as it can. Burning CPU on a spin - * loop steals cycles from APU/emulation threads and the "right" - * threshold varies wildly across Android devices anyway. - */ - int64_t now = android_monotonic_time_ns(); - if (now < deadline) { - android_sleep_until_ns(deadline); - now = android_monotonic_time_ns(); - } - if (now - deadline > frame_interval) { - last_update = now; - } else { - last_update = deadline; - } -#else - const int64_t sleep_threshold = -#ifndef _WIN32 - 2000000; -#else - 250000; + int64_t deadline = last_update + 16666666; #endif #ifdef DEBUG_XEMU_C @@ -1814,31 +1761,38 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) int64_t spin_acc = 0; #endif +#ifdef __ANDROID__ + const int64_t sleep_threshold = 500000; // 0.5ms — Android CFS scheduler jitter is ~0.2ms +#elif !defined(_WIN32) + const int64_t sleep_threshold = 2000000; +#else + const int64_t sleep_threshold = 250000; +#endif + while (1) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); int64_t time_remaining = deadline - now; if (now < deadline) { if (time_remaining > sleep_threshold) { + // Try to sleep until the until reaching the sleep threshold. sleep_ns(time_remaining - sleep_threshold); #ifdef DEBUG_XEMU_C - sleep_acc += qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - now; + sleep_acc += qemu_clock_get_ns(QEMU_CLOCK_REALTIME)-now; #endif } else { + // Simply spin to avoid extra delays incurred with swapping to + // another process and back in the event of being within + // threshold to desired event. #ifdef DEBUG_XEMU_C spin_acc++; #endif } } else { DPRINTF("zzZz %g %ld\n", (double)sleep_acc/1000000.0, spin_acc); - if (now - deadline > frame_interval) { - last_update = now; - } else { - last_update = deadline; - } + last_update = now; break; } } -#endif } @@ -1979,12 +1933,7 @@ static void *call_qemu_main(void *opaque) /* Note: only supports millisecond resolution on Windows */ static void sleep_ns(int64_t ns) { -#ifdef __ANDROID__ - struct timespec sleep_delay; - sleep_delay.tv_sec = ns / 1000000000LL; - sleep_delay.tv_nsec = ns % 1000000000LL; - clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep_delay, NULL); -#elif !defined(_WIN32) +#ifndef _WIN32 struct timespec sleep_delay, rem_delay; sleep_delay.tv_sec = ns / 1000000000LL; sleep_delay.tv_nsec = ns % 1000000000LL; @@ -1994,28 +1943,6 @@ static void sleep_ns(int64_t ns) #endif } -#ifdef __ANDROID__ -static int64_t android_monotonic_time_ns(void) -{ - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; -} - -static void android_sleep_until_ns(int64_t deadline_ns) -{ - struct timespec deadline; - - deadline.tv_sec = deadline_ns / 1000000000LL; - deadline.tv_nsec = deadline_ns % 1000000000LL; - - while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL) == - EINTR) { - } -} -#endif - #ifdef _WIN32 static const wchar_t *get_executable_name(void) { diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 3788821bc7..f4866d1bf7 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -61,8 +61,8 @@ void MainMenuGeneralView::Draw() #if defined(__x86_64__) SectionTitle("Performance"); - Toggle("Hard FPU emulation", &g_config.perf.hard_fpu, - "Use hardware-accelerated floating point emulation (requires restart)"); + Toggle("Native Floats (JIT)", &g_config.perf.fp_jit, + "Use native float storage and inline FP ops (requires restart)"); #endif Toggle("Cache shaders to disk", &g_config.perf.cache_shaders,