diff --git a/docs/launcher.md b/docs/launcher.md
index f69ca8a0..c3999d40 100644
--- a/docs/launcher.md
+++ b/docs/launcher.md
@@ -301,9 +301,11 @@ The stub swaps `python.next` in while no Python runs, then starts the new versio
list was prepared from another shader seed than the one this kit writes: `shader-prepared.bin` carries the digests
of the two seed files and the translator identity (`runtime/prp_file.h`), the step writes the seed first
(`prepare_shaders.py --no-run`, seconds) and compares (`make_game.py` `seed_changed`); a list from before it
- recorded its seed is prepared again once. Another translator alone does not prepare again. The step still builds
- the prepared shaders into the graphics driver's cache again (`make_game.py` `warm_driver_cache`): seconds while
- the driver has them, 20 minutes on a player's PC when the update changed what they are built with. Its bar
+ recorded its seed is prepared again once. Another translator alone does not prepare again. The step then asks
+ the graphics driver's cache whether it still has the prepared shaders (`make_game.py` `warm_driver_cache`,
+ `nx2navi.exe --warm-shaders`: a sample of twelve, as the game checks at every start) and builds them all into it
+ again only when it does not: nothing more while the driver has them, 6 to 20 minutes on a player's PC after a
+ driver update or an update that changed what they are built with. Its bar
follows that count from 0 to 100 %, and the Updates row says an update takes from a few minutes to half an hour
or more (`launcher/ui/app.py` `UPDATE_TAKES`).
8. **Draw the Button Prompts**, with button art in the new version: only when the art, the prompt tools or the key
diff --git a/runtime/vk_pipeline.c b/runtime/vk_pipeline.c
index 302f989b..af38b4fa 100644
--- a/runtime/vk_pipeline.c
+++ b/runtime/vk_pipeline.c
@@ -8026,8 +8026,35 @@ static void lb_warm_worker(void *arg) {
}
}
-// Preparation: every variant of
/shader-prepared.bin on every core at normal priority.
-static void lb_warm_prepared(const char *dir) {
+// --warm-shaders, before the whole list: LW_SAMPLE prepared fragment libraries spread over it, built into the driver's
+// own cache alone (no_cache, as the library warm in play builds) and timed, as the game's check at every start does
+// (lw_check). 1 when at least LW_WARM_SHARE of them came from the driver's cache. *fast and *got: the counts.
+static int prep_sample_warm(int *fast, int *got) {
+ double ms[LW_SAMPLE];
+ *fast = *got = 0;
+ size_t step = LB.n / LW_SAMPLE ? LB.n / LW_SAMPLE : 1;
+ int was = no_cache;
+ no_cache = 1;
+ for (size_t i = step / 2; i < LB.n && *got < LW_SAMPLE; i += step)
+ for (size_t j = i; j < i + step && j < LB.n; j++) { // the next fragment variant from here
+ if (LB.e[j].stage != GFX_STAGE_FS) continue;
+ journal_mark(0, &LB.e[j]); // a driver that dies on it names it, as the full pass would
+ double t = lw_build(&LB.e[j]);
+ journal_mark(0, NULL);
+ if (t >= 0) ms[(*got)++] = t;
+ break;
+ }
+ no_cache = was;
+ for (int i = 0; i < *got; i++) *fast += ms[i] <= LW_COLD_MS;
+ return *got >= LW_SAMPLE / 2 && lw_warm_sample(*fast, *got);
+}
+
+// Preparation: every variant of /shader-prepared.bin on every core at normal priority. sample: --warm-shaders
+// after an update, which first asks the driver's cache (prep_sample_warm) and builds nothing more when it has them.
+// It took 10 to 20 s on a player's PC at every update, all of it 42,000 libraries the driver gave back at once, and
+// it still takes the minutes of a full build when the driver no longer has them (a driver update, or a change of what
+// they are built with: 380 s on that PC once). NX2NAVI_LIB_WARM=force builds the whole list whatever the sample says.
+static void lb_warm_prepared(const char *dir, int sample) {
char path[PLAT_PATH_MAX * 2 + 32];
snprintf(path, sizeof path, "%s/shader-prepared.bin", dir);
size_t n = 0;
@@ -8045,6 +8072,20 @@ static void lb_warm_prepared(const char *dir) {
LB.n = n, LB.idle = 0, LB.t0 = now_ms();
static const uint32_t no_pr[16], no_fs[4];
lb_state(no_pr, no_fs);
+ int fast = 0, got = 0;
+ if (sample && P.gpl && strcmp(cfg_value("NX2NAVI_LIB_WARM"), "force") && prep_sample_warm(&fast, &got)) {
+ if (SK.journal) plat_file_close(SK.journal), SK.journal = NULL, plat_file_delete(jp);
+ printf("prepare-shaders: progress build %zu %zu\n", n, n);
+ printf("prepare-shaders: the graphics driver's cache still has the prepared shaders (%d of %d sampled came from "
+ "it in under %.0f ms each): the %zu variants are not built again (%.0f ms)\n", fast, got, LW_COLD_MS, n,
+ now_ms() - LB.t0);
+ fflush(stdout);
+ free(LB.e), LB.e = NULL, LB.n = 0, LB.next = 0, LB.done = 0, LB.skipped = 0;
+ return;
+ }
+ if (sample && got)
+ printf("prepare-shaders: the graphics driver's cache does not have the prepared shaders (%d of %d sampled came "
+ "from it): all %zu variants are built into it again\n", fast, got, n);
int cpus = (int)plat_cpu_usable(), k = 0;
PlatThread *t[JR_SLOTS];
for (int i = 0; i < cpus && i < JR_SLOTS; i++)
@@ -8281,7 +8322,7 @@ int vk_pipeline_prepare_shaders(void) {
// The listed pipelines before the stage libraries: the pipeline cache is saved with them in it and without the
// tens of thousands of libraries the next step builds, which would take the file past its size limit.
if (!rc) seed_pipelines(dir);
- if (!rc) lb_warm_prepared(dir);
+ if (!rc) lb_warm_prepared(dir, 0);
return rc;
}
@@ -8304,7 +8345,7 @@ int vk_pipeline_warm_shaders(void) {
return 1;
}
seed_pipelines(dir); // before the libraries, as in vk_pipeline_prepare_shaders
- lb_warm_prepared(dir);
+ lb_warm_prepared(dir, 1);
return 0;
}