perf(vk): the shader caches' stamp leaves out vk_api.h, so a menu, input or audio change keeps every player's translated programs #370
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!370
Loading…
Reference in a new issue
No description provided.
Delete branch "HumanBean/Longshot:perf/translator-stamp"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Every change to
gpu/vk/vk_api.h, even a new menu, controls or audio option id that has nothing to do with shaders, threw away every player's translated programs and Vulkan pipeline cache at their next start: about 16,000 programs translated again (46.5 s on 12 workers of a Ryzen 9 7900X, plus 49 s of pipeline builds with a warm driver cache, far longer on a slow PC). Since v3.0.0-rc.3, 3 of the 7 commits that did this were menu rows alone.What changed
nx2/translator_files.pynames the files the shader caches' translator stamp (LONGSHOT_SHADER_STAMP) is made of and computes it: every file undergpu/vk/{eden,sirit,compat}/, thenvk_shader.cpp,vk_bank.cpp,vk_state.h, in the same order and with the same CRLF handling as before.vk_api.his no longer stamped. Its docstring says why each file is in and whyvk_api.his out.nx2/build.pymakes-DLONGSHOT_SHADER_STAMPwithtranslator_files.stamp(vk), replacing the inline hash.translator_stamp()ingpu/vk/vk_shader.cppand the cache formats are unchanged.nx2/bake.py,nx2/bake_mimo.pyandnx2/bake_ds/bake_all.pyeach kept their own copy of the old formula. All three now call the module.scripts/dev_packs.pytranslator_changed()decides "prepares again" from the packed checkout's ownnx2/translator_files.py(stamped()), or from a change to that module itself. A checkout from before the module uses the old file list.tests/test_shader_stamp_inputs.pyrewritten: build.py uses the module; avk_api.hedit or another renderer source keeps the stamp; an edit to each stamped file, a new recompiler file or avk_state.hfield changes it.tests/test_program_cache_format.py,tests/test_shader_stamp_cache.py(its harness execs build.py's Vulkan branch and now gets the module in its namespace),tests/test_dev_packs.py(the prepare rule on an authored tree, the old-checkout fallback) andtests/test_bake_skeleton.py(the appended-order trap, now also checked positively) were updated in the same PR.docs/development.md(what the stamp holds, under the switches' intro; the dev packs' Prepare rule),docs/BAKE.md, and the comment at the top ofgpu/vk/vk_progcache.cpp.Why taking
vk_api.hout is safeWhat decides a translated program's SPIR-V is the stamped sources, plus what
translator_stamp()folds in at run time: the depth mode, the GPU's profile, the vendor fixes, the switches,sizeof(Shader::Info),sizeof(Program)and the C++ library. The guest code andNvnStateare read through the host. Fromvk_api.h, the translator (vk_shader.cpp,vk_bank.cpp, eden, sirit, compat) uses only theseMkwVkHostcalls:gpu_read,gpu_guest,guest_base,guest_size: they carry the game's memory. What they return is decided byhost/, which was never stamped. A change to how they are called is a change tovk_shader.cpporvk_bank.cpp, and those stay stamped.depth_minus_w: its answer is already folded intotranslator_stamp()at run time.None of those files uses any
HOS_*,MkwVk*orMKWVK_*name (grep). The NVN typesNvnStateis built from come fromgpu/render/rx_nvn.h.vk_api.hincludes that header, but the stamp never covered it, before or after this PR. Program records hold novk_api.htype:vk_progcache.cpp'skProgramCacheFormatand its static_asserts cover what a record holds.vk_state.hstays stamped.vk_bank.cpp's driver banks andvk_shader.cppreadNvnState'sprog,ubo,ssbo,tex,samp,img,vattr,nvattr,cs_push,gridandtex_gen, with their array sizes, and the seeds are translated from a default-constructedNvnState. A field that only the replay uses (cd906265's occlusion counters) therefore still moves the stamp. SplittingNvnStatewould be a refactor of the replay and is not in this PR.Considered and left out: ignoring comment-only and whitespace-only edits in stamped files. Over the 127 commits measured below, only 2 were comment-only (
f3fe1763,9cffaeb3). A wrong C/C++ comment stripper could keep a stale cache, which is far worse than the waste it saves, so the stamp still hashes the files byte for byte.Flags and settings
No switch, menu row or ini key. The existing build define the stamp reaches the renderer through, listed for completeness:
LONGSHOT_SHADER_STAMP(build define, set bynx2/build.py)translator_stamp()folds into the shader caches' stamp: now withoutvk_api.hMeasurements
main:
0caf0618(2026-11-14)this PR: perf/translator-stamp @
48bb6d8aHardware: none needed. The stamp is a hash of source files, the same on every PC (computed here on Linux, Python 3.14).
Scene: none (game-free)
Method: a script of my own, outside the repo, that reads each revision's files with
jj file show -rand computes the old formula (main'snx2/build.py) and the new one (nx2/translator_files.py) for each commit and its parent. It was run twice, and both runs gave the same stamps.translator_files.stamp()on this PR's tree givesfcc354fdcc02cb5c, the new-rule value for main in the table below, so the table's new rule is the module's.How to reproduce:
python3 -I -c "import sys; sys.path.insert(0, 'nx2'); import translator_files as t; print(t.stamp('gpu/vk'))"at any commit with this PR'snx2/translator_files.py(copy it in for an older commit). Compare with the old block in main'snx2/build.py.0caf061848bb6d8aHOS_OPT_*id invk_api.hvk_bank.cpp(seed_cs_control = nullptr→= {})Every commit since v3.0.0-rc.3 (
0c74253d) that touched a stamped file, oldest first:cd906265vk_state.h: newNvnStatefieldsvk_state.hfield)7315957cvk_shader.cppf3673ee4vk_api.h:HOS_OPT_DAMAGE_TAKENf3fe1763vk_state.h: comments onlye65cb1e0vk_api.h:HOS_OPT_HOLD_TOGGLE9ef17269vk_api.h:HOS_OPT_AUDIO_OUTPUT57f4eaecvk_shader.cppBefore rc.3, the 52 commits that would have kept the caches were all
vk_api.h-only: menu rows, controls, prompts, motion, the microphone, the control bridge, the DLSS jitter ids.9cffaeb3, a comment-onlyvk_state.hcommit, still counts under the new rule.For players on releases, v3.0.0-rc.3 to main still re-translates (
7315957cand57f4eaecchange the translator). The gain is for nightlies and for future releases whose stamped changes are only menu, input or audio ids. This PR itself changes the stamp value once (fe6ea2e4 → fcc354fd, becausevk_api.hleaves the hash), so the first build that carries it re-translates one last time.Testing
Tested: the stamp table and the
vk_api.h/vk_bank.cppedits above (game-free, Linux). By reading: every check undertests/that reads the code this PR changes (the build.py stamp block, the dev_packs list, the bake mirrors). No tests were run on the author's PC (nottests/run_all.py, not any single check). Forgejo's CI runs them.Not tested: a game run. The game sees only the stamp's value, a constant compiled into the renderer, and the table shows that value for each tree. Not run: a
dev_packs.py packof avk_api.h-only change, to show it builds withoutpreparesand the game keepslongshot_programs.binat start.Needs other testers: None (the change is a source hash, the same on every GPU and OS)
Screenshots
Nothing on screen
Risk and rollback
The risk is a stale cache if the translator came to depend on something in
vk_api.h: the programs would be kept from a translator that read it differently. Today it reads nothing there but the host calls above. A future change that makes the translator use avk_api.hdeclaration must stamp it: add the header toFILESinnx2/translator_files.py, or move the declaration into a stamped file.tests/test_shader_stamp_inputs.pypins the list, so changing it means stating why. Rollback: revert the commit, which gives back the old stamp (and one more re-translation).Reviewer notes
Start with
nx2/translator_files.py(the list and the reasons), thenscripts/dev_packs.pytranslator_changed. The tests undertests/were updated without being run here: please check CI's run oftests/test_shader_stamp_inputs.py,test_shader_stamp_cache.py,test_program_cache_format.py,test_dev_packs.py,test_bake_skeleton.pyandtest_bake_ds.py.Issues
None.
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). No tests run on the author's PC; CI runs them.third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.tests/that reads changed source updated in this PR, not weakened.gpu/vk/vk_progcache.cpp.)🤖 Generated with Claude Code
https://claude.ai/code/session_017vw8e34avQ5cTCWow16MXn
The translator stamp (LONGSHOT_SHADER_STAMP) hashed gpu/vk/vk_api.h whole, though the translator reads nothing from it but the host's memory and depth mode through MkwVkHost: every Insert menu, controls or audio row added there dropped the program cache, the prepare record and the Vulkan pipeline cache for every player. Of the 7 commits since v3.0.0-rc.3 that moved the stamp, 3 were vk_api.h's option ids alone; of the 120 before that, 52. nx2/translator_files.py now names the stamped files (gpu/vk/{eden,sirit,compat}/, vk_shader.cpp, vk_bank.cpp, vk_state.h) and makes the stamp; nx2/build.py, the bake tools and scripts/dev_packs.py's prepare rule all read it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017vw8e34avQ5cTCWow16MXnDescription: Needs other testers written as None, the LONGSHOT_SHADER_STAMP build define listed under Flags, and the host-code box added for the one C++ edit (a comment in vk_progcache.cpp).
Read head
48bb6d8aagainst main0caf0618: the complete diff, description, sole comment, empty reviews, stamp producers and cache consumers; ran the changed stamp and pack checks, selected bake checks, and cache-record and prepare checks with GCC and Clang. Results and the bake exclusions are below. No game, full build or timing run.What's good
vk_api.hinto the value that rejects translated programs, preparation records and pipeline-cache files. The history from0c74253dto0caf0618contains 62 commits and 7 changes to the old input set, versus 4 to the new set. The 3 excluded changes only add Damage taken, Hold-to-toggle and Audio output declarations to this header.vk_state.hstays included, including its defaults and array sizes. The build, all three bake paths and the development-pack prepare decision consume the new definition.nx2/build.py, lines 1102 and 1211-1212, still makes host and renderer objects depend on it. The executable and renderer also retain their API-version checks.Cache safety
I found no current shader-affecting declaration that this change silently loses, but the distinction is narrower than saying the translator reads nothing from this header:
gpu/vk/vk_shader.cpp, lines 968-1001 and 1089-1112;gpu/vk/vk_draw.cpp, lines 2264-2289).gpu_readsupplies shader/control/constant bytes;gpu_guestparticipates in pointer classification;guest_baseandguest_sizehelp validate registration pointers. Their implementations are outside this header. Shader/control bytes are hashed into program identity, and texture/image answers are checked before reuse.depth_minus_wdirectly changes emitted depth conversion and remains explicitly included intranslator_stamp()at lines 1246-1247. These checks should not be described as hashing every possible callback result.translator_stamp()retains the mesh mode, subgroup/alignment settings, float capabilities, vendor/matrix settings, environment switches and record ABI/library values.program_cache_stamp()adds the cache format; file and index readers require that stamp. Pipeline-cache loading still checks the vendor, device and Vulkan cache UUID, and the warm-pass stamp additionally includes driver version and recipe. The driver receives current shader modules and pipeline creation parameters; its opaque cache is not a substitute for validating Longshot's translated-program cache.rx_nvn.his included through this header but was not hashed transitively before this PR either. Neither point is a newly introduced defect at this head.Must change
No functional defect found in this patch. It merges cleanly with main
0caf0618.Measurements and smaller points
Correct the CI claim before taking this out of WIP. The description says the checks were left to CI, but the source-check and SPIR-V jobs on this head are skipped, not passed: https://git.magicbean.dev/MagicBeanDev/Longshot/actions/runs/4397. Current
CONTRIBUTING.mdalso says the full checks are not automatic. Record actual local or manually started results, including Windows coverage.Keep the historical hash result separate from a startup-time saving. The 7-to-4 history comparison supports fewer invalidations. Repeating a deterministic hash is valid evidence for that property, not an interleaved timing comparison. The quoted 46.5 s translation and 49 s pipeline costs have no paired before/after logs, median or spread here. To quantify the saving, compare a populated-cache update containing only a header option change on named builds, on the same PC, with at least two alternating runs per side. Also keep the disclosed one-time invalidation when this new formula first ships.
Add a direct header-edit case to the existing build-key check.
tests/test_shader_stamp_inputs.pyproves the new membership rule on authored bytes, andtest_shader_stamp_cache.pyexecutes the real job-construction branch. A case that edits onlyvk_api.hand asserts both an unchanged shader stamp and changed host/renderer object keys would pin the important distinction above. The current checks do not prove cache reuse in a running game or that arbitrary future additions to the excluded header are safe.Overlaps
nx2/build.py; the latter also changestests/test_shader_stamp_cache.py. Both merge pairwise with this head. Preserve the shared stamp import and its test namespace when combining the dependency-tracking change.Checks and remaining evidence
Passed here:
test_shader_stamp_inputs,test_shader_stamp_cache(10 tests),test_program_cache_format,test_dev_packs,test_program_cache_index(5 tests), andtest_prepared_stampsandtest_program_record_headwith both GCC and Clang.test_bake_skeletonpassed its 6 selectedBakeSkeletonTestsandBakeStampTrapTests;test_bake_dspassed enumeration, parity and key checks and explicitly skipped native translation/bank checks because no offline executable was supplied.Not run: the full suite, full bake translation/verification,
test_bake_mimo, Windows execution, real cache-file reuse across an update, simultaneous game processes, or clean/incremental game builds. The stamp helper has no persisted state or shared output; it hashes the files afresh. Existing program-cache locking, partial-record recovery and development-pack serialization are unchanged. The decisive remaining runtime check is a warm-cache header-only update that keeps translated programs, followed by a translator or depth-mode change that rejects them, on Linux and Windows, with retained startup logs and unchanged rendered output.WIP: perf(vk): the shader caches' stamp leaves out vk_api.h, so a menu, input or audio change keeps every player's translated programsto perf(vk): the shader caches' stamp leaves out vk_api.h, so a menu, input or audio change keeps every player's translated programs