Skip to content

Program cache: one head reader for both readers of a record (the prediction finds its programs again) - #1051

Merged
NotRexed merged 1 commit into
NotRexed:mainfrom
hckrman101:program-record-head
Nov 9, 2026
Merged

NotRexed merged 1 commit into
NotRexed:mainfrom
hckrman101:program-record-head

Conversation

@hckrman101

@hckrman101 hckrman101 commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

program_cache_each reads a program record's head only as far as shared memory. Since the mesh shader fallback came back (#640), program_cache_save writes five more words after it: the fallback's topology, vertices per workgroup, record words, payload words and task threads. program_cache_each takes the first two of them for the stage count, which is 0 for every program that is not a fallback one, so no kept program ever reaches the prediction (#772). A fresh start's log shows it: vk: predicted 0 pipelines from 1001 notes for 0 program variants no note names: 0 made in 2.4 s.

Change

  • One head, written and read in one place: put_head writes a record's head (kind, texture answers, questions, workgroup, shared memory, the fallback's five words) and get_head reads it. program_cache_save uses the first; program_cache_read and program_cache_each both use the second, so the two readers can no longer disagree on the format.
  • No format change: the bytes written are the same as before, so kept caches stay good.
  • Test: tests/test_program_record_head.py builds put_head/get_head as they are in the source and round-trips the head of a mesh fallback program and of an ordinary one, checking every field and that the stage count is read right after them. It also checks that the save and both readers use those two functions.

Measured

RTX 3090, Linux, fresh first start (no caches), headless, the same Kokiri Forest route with the shipped notes (1,001 pipelines), one run each, nothing else on the machine. Current main already has #939's state list.

main (prediction finds nothing, as now) this PR (prediction works)
prediction at start predicted 0 pipelines … 0 made in 2.2 s predicted 13492 pipelines … 13492 made in 162.5 s
pipelines built in play 49 15
programs translated in play 4 10
draws left out (pop-in) 348 120
frames over 50 / 100 ms 3 / 0 5 / 1
worst frame 58 ms 124 ms

The trade-off, for a maintainer decision: this makes #772's prediction run as designed, which adds about 160 s to the first start after an update (13,492 pipelines into the driver's cache, on top of the ~165 s shader preparation). With #939's state list already covering most of what play needs, the gain in play on this route is small (49 → 15 pipelines built in play, 348 → 120 draws left out; frame times within run-to-run noise). This PR only repairs the reader; if the prediction's cost isn't worth it any more, it could be capped or turned off on purpose in a follow-up rather than staying off by accident.

With #1026 the cost moves into the build. The prediction already runs in the build-time path (--prepare-shaders / HOOKSHOT_PREPARE_ONLY: prepare, then prediction, then precompile, into the driver's cache), so once #1026 is in, these ~160 s are spent under the builder's progress bar instead of before the game's first start, and the first start gets the predicted pipelines for free. The cost at first start then only applies to builds made without preparing shaders.

…rediction gets its kept programs again

program_cache_each read a record's head only up to shared memory and took the mesh fallback's first two words (written since the fallback came back, NotRexed#640) for the stage count: 0 for every ordinary program, so no kept program reached the prediction ("predicted 0 pipelines ... for 0 program variants"). The head is now written by put_head and read by get_head, in program_cache_save, program_cache_read and program_cache_each alike. tests/test_program_record_head.py round-trips a fallback and an ordinary program's head.
@klimovich008

Copy link
Copy Markdown
Contributor

Confirmed on Windows with the actual program_cache_save and program_cache_each, using synthetic graphics/fallback/mesh/compute records: old code emits 0/3 expected graphics callbacks; this version emits all 3 with matching stage words, and compute stays excluded. Both writers produce identical bytes. Truncated record prefixes emit no callbacks.

One wording correction: ordinary defaults (topology 3, vertices 1) yield a bogus stage count of 4294967299, rather than zero. The reader bug and fix still hold.

Shader::Info tails were stubbed; no GPU or game test.

NotRexed added a commit that referenced this pull request Nov 9, 2026
… 0: none)

With #1051 the prediction found its programs again and predicted ~28000 pipelines: a first start after an update spent many minutes on them before the game's first frame. Those left are built in play when first drawn, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NotRexed
NotRexed merged commit 2e40f27 into NotRexed:main Nov 9, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants