From 28a69d23cc5e56fbb21f3a01e484cb280e0f7a27 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Fri, 13 Nov 2026 17:15:48 -0500 Subject: [PATCH] Write the GPU-drawn textures of a hard-count batch back in one upload batch before the drops. Past RELEASED_HARD, sweep_hard drops up to HARD_BATCH released entries in one walk (2969005e). drop_tex writes a gpu_dirty entry back for DROP_HARD when the fence write-back would take it, and each write_back_n(&e, 1) call ended in vk_upload_flush(1), a GPU round trip. A batch with many such entries waited once per entry, under tex_lock, on the thread that releases, the replay's overwrite_others included. The rule before dropped one entry per release, so it waited at most once. Now sweep_hard gathers the picked entries the same way drop_tex decides on them: an entry whose storage the CPU rewrote since the GPU drew into it loses gpu_dirty, and one wb_wanted takes is written back. All of them go back in one write_back_n call, one upload batch with one wait. write_back_n clears gpu_dirty, so drop_tex then finds nothing to write back. The same entries go, in the same order, and the entries written back are counted in n_drop_wb as before. Not reproduced before the change: the 0.10.0 session never reached RELEASED_HARD, and no check without a device covers write_back_n. Checks that passed: pass_cc_check on vk_texture.c, no new -Wall -Wextra warnings, tex_release_check, tex_compact_check. Co-Authored-By: Claude Opus 5.5 --- runtime/vk_texture.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/runtime/vk_texture.c b/runtime/vk_texture.c index a9975e34..52c8d560 100644 --- a/runtime/vk_texture.c +++ b/runtime/vk_texture.c @@ -2439,6 +2439,21 @@ static void sweep_hard(void) { // tex_lock held } int need = nreleased - (RELEASED_HARD - HARD_SLACK); int k = trl_hard_pick(cls, n, need < HARD_BATCH ? need : HARD_BATCH, out); + // The picks the GPU drew into go back to their storage first, in one upload batch with one wait + // (write_back_n), as drop_tex would write each of them back for DROP_HARD with a GPU wait of its own: up to + // HARD_BATCH waits on the thread that releases, the replay's overwrite_others included. write_back_n clears + // gpu_dirty, so drop_tex then finds nothing to write back; the same entries go, in the same order. + static Tex *wbv[HARD_BATCH]; + int m = 0; + wb_cfg_init(); + for (int i = 0; i < k; i++) { + Tex *e = walk[out[i]]; + uint32_t t; + if (!e->gpu_dirty) continue; + if (storage_rewritten(e, e->gpu_gen, &t) == 1) e->gpu_dirty = 0; + else if (wb_wanted(e)) wbv[m++] = e; + } + if (m) write_back_n(wbv, m), n_drop_wb += (uint64_t)m; for (int i = 0; i < k; i++) n_hard_by[cls[out[i]]]++, drop_tex(walk[out[i]], DROP_HARD); if (!k) hard_lru_drop(lru); }