From 6daabc0e374c52cd83f60130ca7b5c32914a000b Mon Sep 17 00:00:00 2001 From: jfpaardoo Date: Mon, 9 Nov 2026 20:31:22 +0100 Subject: [PATCH] vulkan: Link unskippable pipelines inline and help drain build jobs during stalls (Fixes #591) --- runtime/nvn/vulkan/exec.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/runtime/nvn/vulkan/exec.c b/runtime/nvn/vulkan/exec.c index b5bd1468..d9be3be7 100644 --- a/runtime/nvn/vulkan/exec.c +++ b/runtime/nvn/vulkan/exec.c @@ -1098,6 +1098,20 @@ static void *worker(void *a) { return NULL; } +static bool job_run_one(void) { + pthread_mutex_lock(&jobs_lock); + Job *j = jobs_head; + if (j) { + jobs_head = j->next; + if (!jobs_head) jobs_tail = NULL; + } + pthread_mutex_unlock(&jobs_lock); + if (!j) return false; + j->fn(j->arg); + free(j); + return true; +} + // Shaders and pipelines compile where they are first needed, the draw waiting for them (a short stutter the first time // an effect appears), unless the "Asynchronous shaders" setting is on: then they compile on other threads and draws // that need them are skipped meanwhile. That loses what the game draws only once: the save file's screenshot (a @@ -7660,7 +7674,7 @@ static bool one_shot_target(void) { // stalls on them - where a first boot linked 236 pipelines in one 109 ms frame. A stage library still to compile // (5-50 ms) always goes to a worker. SAVAGEWIND_PIPE_LINK_MS=0: every build on a worker, as before. static void build_pipeline(void *arg); -static bool link_now(Pipe *pp) { +static bool link_now(Pipe *pp, bool must_happen) { static int64_t budget = -1; if (budget < 0) budget = (getenv("SAVAGEWIND_PIPE_LINK_MS") ? atoi(getenv("SAVAGEWIND_PIPE_LINK_MS")) : 3) * 1000000ll; const Prog *pr = pp->k.prog; @@ -7672,7 +7686,7 @@ static bool link_now(Pipe *pp) { static uint64_t frame, spent; uint64_t now = renderer_present_count(); if (now != frame) { frame = now; spent = 0; } - if (spent >= (uint64_t)budget) return false; + if (!must_happen && spent >= (uint64_t)budget) return false; uint64_t t0 = SDL_GetTicksNS(); build_pipeline(pp); spent += SDL_GetTicksNS() - t0; @@ -7680,10 +7694,24 @@ static bool link_now(Pipe *pp) { return true; } +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) +#define exec_relax() __builtin_ia32_pause() +#elif defined(__aarch64__) || defined(_M_ARM64) +#define exec_relax() __asm__ volatile("yield") +#else +#define exec_relax() ((void)0) +#endif + static void wait_built(_Atomic int *state) { // (0 building: until built or failed, 5 s at most) if (atomic_load(state) != 0 || sync_shaders()) return; uint64_t t0 = SDL_GetTicksNS(); - while (atomic_load(state) == 0 && SDL_GetTicksNS() - t0 < 5000000000ull) SDL_Delay(1); + while (atomic_load(state) == 0 && SDL_GetTicksNS() - t0 < 5000000000ull) { + if (!job_run_one()) { + for (int i = 0; i < 200 && atomic_load(state) == 0; i++) exec_relax(); + if (atomic_load(state) != 0) break; + SDL_Delay(1); + } + } stat_build_waits++; perfcount_add(PC_BUILD_WAITS, 1); perfcount_add(PC_BUILD_WAIT_NS, SDL_GetTicksNS() - t0); @@ -8099,9 +8127,10 @@ static void draw_(CmdRec *r, bool indexed) { pipe_tab[pslot].key = ph; pipe_tab[pslot].pipe = pp; pipe_n++; + bool must = one_shot_target() || draw_must_happen(); VkPipeline ready = sync_shaders() ? VK_NULL_HANDLE : warm_ready(pr->vi, pr->fi, &k.s); if (ready) { pp->pipe = ready; atomic_store(&pp->state, 1); atomic_fetch_add(&stat_warm, 1); } - else if (sync_shaders() || !link_now(pp)) run_job(build_pipeline, pp); // (asynchronous: a link alone is done here) + else if (sync_shaders() || !link_now(pp, must)) run_job(build_pipeline, pp); // (asynchronous: a link alone is done here) } pipe_memo.k = k; pipe_memo.pp = pp;