diff --git a/nx2navi.ini.example b/nx2navi.ini.example index 2ee99ddc..cbb85b6e 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -150,6 +150,9 @@ # skip push constants that did not change. # bind_skip = 1 # shader_memo = 1 +# 1 flushes every shader cache file to the disk before it is used, as earlier versions did; 0 (default) flushes only +# the stored game code, which makes shader preparation about twice as fast (a torn file is translated again). +# shader_cache_flush = 0 # pipe_memo = 1 # tex_memo = 1 # desc_template = 1 diff --git a/runtime/cfg.c b/runtime/cfg.c index 61aeb9b3..6aee9bb4 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -56,7 +56,7 @@ static const char *const known[] = { "REFLEX", "RENDER_SCALE", "REPLAY_ASYNC", "RTSS", "REPLAY_CHECK", "RESOLVE_CHECK", "RESTART_WAIT", "ROMFS", "ROMFS_INDEX", "ROMFS_MMAP", "ROMFS_MODS", "ROMFS_PATCHES", "ROMFS_PROOF_DIR", "ROMFS_VERIFY", "RSTICK_DEADZONE", "RUMBLE", "RUMBLE_CURVE", "RUMBLE_STRENGTH", "RUMBLE_TRIGGERS", "SHADER_CACHE", - "SHADER_MEMO", "SHADOW_COMPARE", "SHADOW_RANGE", "SHADOW_RES", "SHADOW_SCALE", "SHARED_FONTS", "SHARPNESS", "SHOTS", "SHOTS_DIR", + "SHADER_CACHE_FLUSH", "SHADER_MEMO", "SHADOW_COMPARE", "SHADOW_RANGE", "SHADOW_RES", "SHADOW_SCALE", "SHARED_FONTS", "SHARPNESS", "SHOTS", "SHOTS_DIR", "SVC_SLOW_MS", "SHOTS_FULL", "SPEED_PROBE", "SPIN", "SPIN_HANDOFF_US", "SPLIT_DRAWS", "SPLIT_US", "SPV_OVERRIDE", "STICK_DEADZONE", "STICK_SENS", "SWKBD_ANSWER", @@ -110,7 +110,7 @@ static const struct { const char *key, *section; } sections[] = { {"LIB_PREBUILD", "performance"}, {"LIB_PREBUILD_THREADS", "performance"}, {"PIPE_PREBUILD", "performance"}, {"PREWARM_XLATE", "performance"}, {"GPL", "performance"}, {"ASTC", "performance"}, {"MEMORY", "performance"}, {"TEX_ALIAS", "performance"}, {"TEX_VRAM_PRESSURE", "performance"}, {"ASTC_BC", "performance"}, {"LOD_GUARD", "performance"}, {"FIX_C0", "performance"}, {"FIX_ZERO", "performance"}, - {"BIND_SKIP", "performance"}, {"SHADER_MEMO", "performance"}, {"PIPE_MEMO", "performance"}, {"TEX_MEMO", "performance"}, {"DESC_TEMPLATE", "performance"}, + {"BIND_SKIP", "performance"}, {"SHADER_CACHE_FLUSH", "performance"}, {"SHADER_MEMO", "performance"}, {"PIPE_MEMO", "performance"}, {"TEX_MEMO", "performance"}, {"DESC_TEMPLATE", "performance"}, {"PUSH_SKIP", "performance"}, {"SPLIT_DRAWS", "performance"}, {"SPLIT_US", "performance"}, {"VK_VALIDATION", "performance"}, {"VK_DYN_VERTEX", "performance"}, {"VK_DYN_A2C", "performance"}, {"VK_SUBGROUP_PIN", "performance"}, {"VK_RECONVERGE", "performance"}, {"TRACE", "diagnostics"}, {"FLIGHT", "diagnostics"}, {"PASS_CRUMBS", "diagnostics"}, {"FLIGHT_SPIKE_MS", "diagnostics"}, @@ -209,7 +209,7 @@ static const CfgMeta metas[] = { {"ASTC_SCRATCH_KEEP", "0", "0|1", RESTART}, {"ASTC_ARENA", "0", "0|1", RESTART}, // vk_astc.c scratch_get, vk_texture.c astc_arena // render fixes {"LOD_GUARD", "1", "0|1", RESTART}, {"FIX_C0", "1", "0|1", RESTART}, {"FIX_ZERO", "1", "0|1", RESTART}, - {"BIND_SKIP", "1", "0|1", RESTART}, {"SHADER_MEMO", "1", "0|1", RESTART}, {"PIPE_MEMO", "1", "0|1", RESTART}, {"TEX_MEMO", "1", "0|1", RESTART}, + {"BIND_SKIP", "1", "0|1", RESTART}, {"SHADER_CACHE_FLUSH", "0", "0|1", RESTART}, {"SHADER_MEMO", "1", "0|1", RESTART}, {"PIPE_MEMO", "1", "0|1", RESTART}, {"TEX_MEMO", "1", "0|1", RESTART}, {"BIND_REUSE", "1", "0|1", RESTART}, {"DYN_DELTA", "1", "0|1", RESTART}, {"DESC_TEMPLATE", "1", "0|1", RESTART}, {"PUSH_SKIP", "1", "0|1", RESTART}, {"SPLIT_DRAWS", "64", "0..1000", RESTART}, diff --git a/runtime/gfx_shader.c b/runtime/gfx_shader.c index ecd1e4ce..d54fe8a1 100644 --- a/runtime/gfx_shader.c +++ b/runtime/gfx_shader.c @@ -1063,11 +1063,20 @@ static int use_find(const uint8_t h[32], UseMask *m) { return known; } +// NX2NAVI_SHADER_CACHE_FLUSH (default 0): 1 flushes the .spv, .res.json and .use files to the disk too, as the cache +// did before (see "Durability" at the top), for a drive or setup where a torn file was seen. +static int cache_flush(void) { + static int on = -1; + if (on < 0) on = rt_knob("SHADER_CACHE_FLUSH", "0") == 1; + return on; +} + static void use_store(const uint8_t h[32], const UseMask *m) { char p[PATH_CAP + 80]; use_path(h, p); uint32_t f[5] = {USE_MAGIC, USE_VERSION, m->slots, m->attrs, m->fouts}; - plat_file_write_cached(p, f, sizeof f); // not flushed: a torn mask fails its magic and size check (see the top) + if (cache_flush()) plat_file_write_atomic(p, f, sizeof f); + else plat_file_write_cached(p, f, sizeof f); // not flushed: a torn mask fails its magic and size check (see the top) plat_mutex_lock(&cache_lock); use_put(h, 1, m); plat_mutex_unlock(&cache_lock); @@ -1529,6 +1538,7 @@ static const GfxShader *translate_one(const Req *r, int stage, const uint8_t *co return fail(reply, hex); } // Not flushed (see "Durability" at the top): the translator wrote them whole, and disk_load checks both. + if (cache_flush()) plat_file_flush(spv_path), plat_file_flush(res_path); if (!(s = disk_load(key, r->iid))) return fail("translator output missing or malformed", hex); if (!r->final) { // name the files after the cache key, remember the mask m = use_of(s); @@ -1536,9 +1546,9 @@ static const GfxShader *translate_one(const Req *r, int stage, const uint8_t *co char mhex[65], to[PATH_CAP + 80]; key_hex(mk, mhex); snprintf(to, PATH_CAP + 80, "%s/%s.spv", cache_dir, mhex); - int moved = plat_file_replace(spv_path, to, 0); + int moved = plat_file_replace(spv_path, to, cache_flush()); snprintf(to, PATH_CAP + 80, "%s/%s.res.json", cache_dir, mhex); - moved = moved && plat_file_replace(res_path, to, 0); + moved = moved && plat_file_replace(res_path, to, cache_flush()); if (moved) { use_store(r->code_hash, &m); } else { // keep the mask for this run only