From ddc0247b259336af68394d176ea3091d8fb54e34 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Fri, 13 Nov 2026 18:58:41 -0500 Subject: [PATCH] Count the draws skipped for a pending pipeline with atomics, as the replay adds to the count on one thread while the present takes it on another. pending_draws (vk_exec_draw.c) counts the draws a frame left out because their shader or pipeline was still building; vk_backend.c frame_hold takes it at the present to hold such a frame back. The comment said both happen on the replay thread, which is true only with the replay on its own thread. With the replay on the submitting threads, draw_skipped adds on the thread holding the submission lock (and its parallel chunks) while vk_exec_pending_draws_take reads and clears it on the presenting thread, so a plain ++ and a read-then-clear could lose a count: a frame missing draws shown instead of held, or the next frame held for nothing. The add, the take (an exchange) and the read that skips a link wait are atomics now. Not reproduced before the change. Checks that passed: pass_cc_check of vk_exec_draw.c with no new -Wall -Wextra warnings. Co-Authored-By: Claude Opus 5.5 --- runtime/vk_exec_draw.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/runtime/vk_exec_draw.c b/runtime/vk_exec_draw.c index d95305c5..9e5e18e2 100644 --- a/runtime/vk_exec_draw.c +++ b/runtime/vk_exec_draw.c @@ -4388,18 +4388,15 @@ static void draw_stats_tick(void) { // The draws skipped since the last present because their shader or pipeline was still building: the picture of that // frame lacks them (a light, a wall, a whole lighting pass). vk_backend.c frame_hold reads it at the present and keeps -// the previous picture on screen instead. Written and read on the replay thread. +// the previous picture on screen instead. The replay and its parallel chunks add to it; the present takes it, on the +// presenting thread, which with the replay on the submitting threads need not be the one replaying: atomics. static uint32_t pending_draws; -uint32_t vk_exec_pending_draws_take(void) { - uint32_t n = pending_draws; - pending_draws = 0; - return n; -} +uint32_t vk_exec_pending_draws_take(void) { return __atomic_exchange_n(&pending_draws, 0, __ATOMIC_RELAXED); } static int draw_skipped(const VkExec *ex, int mesh, int why) { DRS.skipped[mesh][why]++; if (vk_dump_armed) vk_dump_draw_skipped(ex, mesh, why); - if (why == SK_SHADER_PENDING || why == SK_PIPE_PENDING) pending_draws++; + if (why == SK_SHADER_PENDING || why == SK_PIPE_PENDING) __atomic_add_fetch(&pending_draws, 1, __ATOMIC_RELAXED); if (why == SK_PIPE_PENDING) perf_count(PERF_CNT_PIPE_SKIP); // skipped draws only: a lookup that then waited is none if (!(DRS.first[mesh] >> why & 1)) { DRS.first[mesh] |= 1u << why; @@ -4583,7 +4580,7 @@ static int prepare_draw_ex(VkExec *ex, int prim, int32_t count, int restart, int budget = link_budget(); // A frame that has left a draw out already is not shown, or is shown without that draw (pending_draws, // vk_backend.c frame_hold): a wait for another pipeline would only make it later. - wait_ms = pending_draws ? 0 : (uint32_t)(*budget / 1000000); + wait_ms = __atomic_load_n(&pending_draws, __ATOMIC_RELAXED) ? 0 : (uint32_t)(*budget / 1000000); if (wait_ms > LINK_DRAW_MS) wait_ms = LINK_DRAW_MS; linking = wait_ms != 0; if (!wait_ms) budget = NULL;