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 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 18:58:41 -05:00
parent c56a87872e
commit ddc0247b25

View File

@@ -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;