Give back the ASTC decoder's grown scratch 5 s after its last large texture, not at the next upload.

A scratch grown past 32 MiB for a large texture went back to the usual size only when the next ASTC upload asked for one, so in the 2026-11-13 session the decoder held 224 MiB for the 70 s after the first area load and from a later load to the session's end. Each present now gives it back once no texture needed more for 5 s; nothing is made then, the next request makes the usual scratch, and the buffer is freed once the upload batches recorded so far completed, as before. The present only tries the decoder's lock, which can be held while VMA makes a block. astc_scratch_trim = 0 keeps the scratch until the next upload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 12:44:24 -05:00
parent b81a9561ca
commit 1fa8b61b87
5 changed files with 31 additions and 5 deletions

View File

@@ -364,6 +364,9 @@
# With astc = gpu: 1 keeps the decoder's scratch buffer from one upload batch to the next instead of making it again for
# each, to shorten the hitches when an area loads (0 = one per batch).
# astc_scratch_keep = 1
# With astc_scratch_keep = 1: 1 gives back a scratch grown for a large texture 5 s after the last one needed it (up to
# 192 MiB of video memory after an area load); 0 keeps it until the next ASTC upload, as in 0.10.1.
# astc_scratch_trim = 1
# With astc = gpu: 1 stages a texture's ASTC blocks in the shared staging ranges instead of a buffer of their own
# (0 = a buffer each).
# astc_arena = 1

View File

@@ -61,7 +61,7 @@ static const char *const known[] = {
"SKIN_MOTION", // vk_skin_motion.inc
"MVOBJ_ALL", "MVOBJ_BANKS", "MVOBJ_EQUAL", "MVOBJ_EXACT", "MVOBJ_EXEC", "MVOBJ_NOC4", "MVOBJ_ONLY", "MVOBJ_TIMES", "MVPREV_CHECK", "MVPREV_GROW", "MVPREV_SKIP", "MVPREV_TRUST", "MV_CHECK",
"MV_SURVEY",
"PIPE_SEED_BOOT", "ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PACE_HZ", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_RES_FIXED", "PATCH_SCENE", "PATCH_WIDE_ASPECT", "PATCH_WIDE_HUD", "PATCH_WIDE_MOVIE", "PATCH_WIDE_OUT", "PATCH_FREECAM", "PATCH_OCCLUSION_OFF", "PATCH_WIDE_SAVE", "PATCH_WIDE_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPELINE_CACHE_MAX_MB", "PIPELINE_CACHE_PLAY", "PIPELINE_CACHE_PLAY_MB", "PIPE_BINARIES", "PIPE_BINARIES_MAX_MB", "PIPE_FEEDBACK", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SEED", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", "POWER_FULL", "TEX_SAME", "TEX_SAME_BIG", "TEX_FLAT3D", "TEX_KEEP_TAKEN", "TEX_HARD_ORDER", "TEX_STALE_KEPT", "PRESENT_HELD", "HELD_SYNC",
"PIPE_SEED_BOOT", "ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PACE_HZ", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_RES_FIXED", "PATCH_SCENE", "PATCH_WIDE_ASPECT", "PATCH_WIDE_HUD", "PATCH_WIDE_MOVIE", "PATCH_WIDE_OUT", "PATCH_FREECAM", "PATCH_OCCLUSION_OFF", "PATCH_WIDE_SAVE", "PATCH_WIDE_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPELINE_CACHE_MAX_MB", "PIPELINE_CACHE_PLAY", "PIPELINE_CACHE_PLAY_MB", "PIPE_BINARIES", "PIPE_BINARIES_MAX_MB", "PIPE_FEEDBACK", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SEED", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", "POWER_FULL", "TEX_SAME", "TEX_SAME_BIG", "TEX_FLAT3D", "TEX_KEEP_TAKEN", "TEX_HARD_ORDER", "TEX_STALE_KEPT", "ASTC_SCRATCH_TRIM", "PRESENT_HELD", "HELD_SYNC",
"PREPARE_PIPELINES", "PREWARM_FULL", "PREWARM_FULL_S", "PREWARM_WAIT_MS", "PREWARM_XLATE", "PROBE_MOVIE", "PROBE_MOVIE_SHOT", "PROFILE", "PROFILE_CHAINS", "PROFILE_DIR", "PROFILE_FRAMES", "PROFILE_REPLAY", "PROFILE_SLOW",
"POOL_FAST", "POOL_GROUP", "POOL_GROUP_CHECK", "POOL_PRESNAP", "POOL_PRESNAP_RUNS",
"SUBMIT_FINISH_GPU", "SVC_WAITS", "SNAP_WIDEN", "SUBMIT_FINISH_RESULTS", "SUBMIT_FINISH_KINDS", "FOLIAGE", "MIRROR_STAGE", "FLUSH_SKIP_CLEAN", "MIRROR_HOT", "MESH_EMULATION", "MESH_EMU_MB", "MESH_EMU_TASKS", "MESH_EMU_BATCH", "GUEST_OPT", "LOG_TEX_NEW", "DUMP_KEY", "VEC_INDEX_FIX", "PASS_TIMES",

View File

@@ -598,6 +598,7 @@ void vk_texture_split_counts(uint64_t *copies, uint64_t *fills);
VkDeviceSize vk_astc_region_bytes(const VkAstcRegion *r, uint32_t bw, uint32_t bh); // source bytes the region reads
void vk_astc_stats(uint64_t *dispatches, uint64_t *texels); // since start
uint64_t vk_astc_scratch_bytes(void); // the decoder's shared scratch now
void vk_astc_scratch_trim(void); // each present: a grown scratch idle for 5 s goes
// NX2NAVI_ASTC_TIME=1: GPU timestamps around each vk_astc_record's dispatches (logged per footprint and the largest one)
void vk_astc_time_enable(int on);
int vk_astc_time_get(uint32_t bw, uint32_t bh, uint64_t *texels, uint64_t *calls, double *us); // totals so far

View File

@@ -487,7 +487,7 @@ static void mem_barrier(VkCommandBuffer cb, VkPipelineStageFlags2 src, VkAccessF
// only after that batch completed (vk_memory.c: completed batches go to the spare list), so the same handle means the
// open batch, ordered by a barrier, or a finished one. g_lock held.
static struct { VkBuffer buf; VmaAllocation alloc; VkDeviceSize size; VkCommandBuffer cb; VkDevice dev; uint64_t big_ns; } S;
static uint64_t n_scratch_new, n_scratch_reuse;
static uint64_t n_scratch_new, n_scratch_reuse, n_scratch_trim;
// NX2NAVI_ASTC_SCRATCH_KEEP=1 (the default): the scratch outlives its upload batch. Every
// batch goes to gfx_queue, so the reuse barrier at the start of the next batch's first chunk (vk_astc_record) orders it
@@ -560,6 +560,27 @@ static VkBuffer scratch_get(VkCtx *c, VkCommandBuffer cb, VkDeviceSize need, int
return out;
}
// Every present (vk_texture.c vk_texstat_frame): a scratch grown past SCRATCH_MIN goes once no texture needed more for
// SCRATCH_SHRINK_NS, without waiting for the next ASTC upload, which is when scratch_get makes it small again: in a
// session of 2026-11-13 the decoder held 224 MiB for the 70 s after an area load and from a later load to the session's
// end. Nothing is made here: the next request makes the usual scratch, as after scratch_get's own shrink. The buffer
// is freed once the upload batches recorded so far completed (vk_defer_buffer), as there. Only tried: scratch_get holds
// g_lock while VMA makes a buffer, up to 19 ms for a new block, and the present does not wait for that.
// NX2NAVI_ASTC_SCRATCH_TRIM=0: the grown scratch stays until the next upload, as in 0.10.1.
void vk_astc_scratch_trim(void) {
static int on = -1;
if (on < 0) on = rt_knob("ASTC_SCRATCH_TRIM", "1") == 1;
if (!on || !scratch_keep()) return;
if (__atomic_load_n(&S.size, __ATOMIC_RELAXED) <= SCRATCH_MIN || !plat_mutex_trylock(&g_lock)) return;
VkCtx *c = vk_ctx();
if (S.buf && c && S.dev == c->device && S.size > SCRATCH_MIN && kern_now_ns() - S.big_ns > SCRATCH_SHRINK_NS) {
vk_defer_buffer(S.buf, S.alloc);
S.buf = VK_NULL_HANDLE, S.alloc = NULL, S.size = 0, S.cb = VK_NULL_HANDLE;
n_scratch_trim++;
}
plat_mutex_unlock(&g_lock);
}
// The shared scratch's size, for the log's VRAM breakdown (scratches still owned by an upload batch not counted).
uint64_t vk_astc_scratch_bytes(void) {
plat_mutex_lock(&g_lock);
@@ -762,9 +783,9 @@ int vk_astc_record(VkCommandBuffer cb, VkBuffer src, VkDeviceSize src_size, cons
}
if ((n_dispatch + (uint64_t)n) / 1024 != n_dispatch / 1024 || !n_dispatch) {
rt_log("vk: ASTC: %llu regions, %.1f M texels decoded on the GPU so far; shared scratch %llu MiB, made %llu "
"times, reused %llu", (unsigned long long)(n_dispatch + (uint64_t)n), (double)n_texels / 1e6,
(unsigned long long)(S.size >> 20), (unsigned long long)n_scratch_new,
(unsigned long long)n_scratch_reuse);
"times, reused %llu, given back idle %llu", (unsigned long long)(n_dispatch + (uint64_t)n),
(double)n_texels / 1e6, (unsigned long long)(S.size >> 20), (unsigned long long)n_scratch_new,
(unsigned long long)n_scratch_reuse, (unsigned long long)n_scratch_trim);
vk_astc_time_summary();
}
n_dispatch += (uint64_t)n;

View File

@@ -3429,6 +3429,7 @@ static void compact_frame_time(uint64_t d);
void vk_texstat_frame(uint64_t frame, uint64_t d) {
if (compact_on()) compact_frame_time(d);
vk_astc_scratch_trim();
if (ts_log_ms < 0) {
ts_log_ms = cfg_int("NX2NAVI_TEXUP_LOG_MS");
if (ts_log_ms < 0) ts_log_ms = 0;