WIP: feat(vk): one shader cache store for every game folder, so a build whose translation is unchanged prepares nothing #737
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
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/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!737
Loading…
Reference in a new issue
No description provided.
Delete branch "dyst/Longshot:feat/shader-cache-store"
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?
Type: feat · Risk: medium · Tested on: Windows 11, game-free checks and a renderer build (not yet in the game) · Needs: a run in the game with two game folders (Windows and Linux, any GPU)
Why
Every game folder prepares its shaders on its own, about 25 minutes with Compile shaders All on a fast PC, even when another folder on the same PC translated the same shaders with the same translator a minute before: a stable and a nightly side by side, a rebuild into a new folder, each
dev_packs.pyruntime (which also carries a 10 GB copy of the caches). The caches are already stamped by what they are good for (program_cache_stamp), so a store keyed by that stamp lets every folder with the same translation use them in place.The design was agreed with the maintainers, including moving these files out of the game folder, where #321 put them. It complements MagicBeanDev/LongshotLauncher#57 (an update in place keeps its
cache/anyway) and replaces copying caches between folders (MagicBeanDev/LongshotLauncher#78). It mirrors the object and lift stores (#371, #372). No Longshot issue fits (the tracker searched).What changed
With
LONGSHOT_SHADER_CACHE=<folder>(or--shader-cache <folder>), the game keeps its shader caches in<folder>/<stamp>/for what one translation made and<folder>/shared/for the lists good for any build, so a game folder whose stamp is in the store starts with its programs and pipelines and prepares nothing. Unset, empty or0, every file stays in the game folder'scache/as before, and the stamp is not taken any sooner. Nothing sets it for players yet: a launcher PR follows, setting it beside its builds and cleaning up the stamps no build uses.gpu/vk/shader_store.h(new, standard library and the platform's file lock, no Vulkan):open_stamped(at the first stamped file asked for, where the game took the stamp before) andopen_shared(at the first list); each part held by<part>/.lockfor the process's life: open with no sharing on Windows,flockelsewhere, so a tool can tell a part in use whatever it calls the path. A second run of the same stamp keeps that part's files in its owncache/for that run.consume: a game folder'scache/of the same stamp is taken in at a start that holds the store. Files the store lacks are moved in (a rename, else a copy as.partrenamed in); the program-side files only whencache/'s program cache carries this stamp, the pipeline cache only when its stamp file does. Pipeline notes and lists the store has get the records it lacks (merge_records, by their heads: the notes' stamp and record size, each list's magic and layout). A pipeline cache the store has a main file for goes in as a play file, merged at the next start byplay_files_merge. A second run's program cache is left in itscache/: merging two program caches record by record is not done here.folder_key: the compiler's version and the C++ library's), not the stamp alone: the program cache keeps its records'Shader::Infoas it lies in memory, and the stamp has the library's version for libc++ and Microsoft's but none for libstdc++ (HumanBean's point on MagicBeanDev/LongshotLauncher#78). Before the store each version had its folder and its tools, so a cache never met another toolchain's; now one never does in the store either. The stamp itself is unchanged, so nobody's caches go.hand_over): each game folder registers in the stamp it uses (<stamp>/users/, a file per folder naming it) and records it (cache/longshot_shader_store.txt). When a folder starts under another stamp (a new translator, another toolchain), the stamp it used before is renamed aside at once and deleted on a thread of its own, unless another game folder that is still there and still on it uses it, a running game holds it, or keeping was asked for (LONGSHOT_SHADER_CACHE_KEEP=1,--shader-cache-keep: both kept, for an A/B). So a folder holds one set of caches at a time, as without the store. Its old files are not reused under the new stamp: they are of no use to another translator, and a toolchain's change keeps the stamp while the records' layout may differ. A folder an earlier run set aside is swept at a later start.<stamp>/longshot_last_usedsay what each folder uses and when a stamp was last used.gpu/vk/vk_paths.hshader_cache_path/shared_cache_path.vk_draw.cpp(the pipeline notes and what goes with them, the preparation's record, the pipeline cache's folder and its stamp check, the compute and state lists),vk_progcache.cppdisk_init(the program cache),vk_programs.cpp(the module ids) take their files through them. The module ids now go by path beside the programs: before, they usedprogram_cache_path()'s folder, which is "" with the program cache off or held by another Longshot, and the file landed in the working folder. The game folder's own files stay oncache_path: the preparation-skipped marker, the driver's faults, the loader's notes go with the notes.host/hos_main.c--shader-cache <folder|0>setsLONGSHOT_SHADER_CACHEfor the run, over the environment, and--shader-cache-keepsetsLONGSHOT_SHADER_CACHE_KEEP=1; both entry points skip their values, andhost/options_path.c's scan of the arguments (#729) no longer takes either for the image.nx2/make_game.pyGAME_ENV_KEEP: the build's shader preparation keepsLONGSHOT_SHADER_CACHEandLONGSHOT_SHADER_CACHE_KEEP(the otherLONGSHOT_names still go), so the preparation fills the store the game will use;builder/linux/longshot-launchkeeps it too.scripts/dev_packs.pygives every game it starts one store (~/.cache/longshot-shaders,%LOCALAPPDATA%\Longshot\shaders) unlessLONGSHOT_SHADER_CACHEsays otherwise;scripts/shader_store.py(new)lslists a store's stamps (size, last use, in use) andprune --keep Nlets go of the stamps no running game holds and no game folder still uses (its users/ and record;--game <folder>names more), the newest N of the rest kept.docs/development.mdThe shader cache store (the layout, what the game does with it, the A/B case, clean-up), the switch's row, the dev_packs bullet; the README's argument table;docs/design.md's cache note.tests/pipeline_play_cache_test.cpp's stand-ins haveshader_cache_pathbesidecache_path(it compilespcache_initand the stamp check cut fromvk_draw.cpp).tests/test_shader_store.pywithtests/test_shader_store.cpp(the header against temporary folders: off and on, the parts, whatcache/gives the store, the record, a part held; and the renderer's call sites,game_env, dev_packs' names),tests/test_shader_store_tool.py(prune against a lock a child process holds as the game does, and the users' markers). The hand-over's cases intests/test_shader_store.cpp: the old stamp let go; kept for another folder still on it, with keep, or held; a stale marker not counted; a set-aside folder swept.tests/test_options_path.py: neither argument is taken for the image (it fails before the fix).tests/test_prepare_at_build.pyfollowsGAME_ENV_KEEPin both the Python and the shell launcher.Measurements
Verdict: game-free: the store's logic passes against temporary folders on Windows (the stamp's and shared parts, take-in, merges, the record, a held part falling back, prune never letting go of a held or recorded stamp), and the renderer builds with it. What it saves (a second game folder that prepares nothing) is not measured in the game yet: it will be added here when the runs under Must be tested are made, by me or by anyone who runs them first.
Hardware: Windows 11 Pro, Ryzen 9 3900X, 64 GB; llvm-mingw clang 23 (the Builder's v2.0.14 tools)
Scene: none (game-free checks; a renderer-only build,
RX_VK_ONLY)Method:
tests/test_shader_store.py --cxx <clang++>andtests/test_shader_store_tool.pyon this branch; the checks pass or fail, so there is nothing to take a median ofHow to reproduce: the two commands above;
RX_VK_ONLY=1 python -I nx2/build.py --standalonefor the renderera00c485ca45e8c89cache/when the store is first usedRX_VK_ONLY)longshot_vk.dlllinkedFlags and settings
One switch and one argument for the same thing, off by default: the launcher PR turns it on for the builds it makes.
LONGSHOT_SHADER_CACHE=<folder>/--shader-cache <folder>cache/cache/stays anyway); untested: in the gameLONGSHOT_SHADER_CACHE_KEEP=1/--shader-cache-keepTesting
Tested:
tests/test_shader_store.py(the C++ checks with the Builder's llvm-mingw clang) andtests/test_shader_store_tool.pypass, Windows 11.python -I -B tests/run_all.pyon this branch (before its rebase ontoa00c485c, which adds only a skill file) and on main @9e6f231d, with the Builder's llvm-mingw clang (12 at once): this branch 542 passed, 18 failed, 35 skipped; main 540 passed, 18 failed, 35 skipped; the same 18 fail on both (test_appimage_player_build, test_cpm_discovery, test_cpp_preflight, test_crash_dialog, test_env_on, test_fpx_sticky, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_make_game_prepare, test_missing_nup, test_oot_state_tap, test_options_budget_once, test_readback_late, test_spawn_emit, test_tested_configurations, test_vk_pgo, test_walk_key).tests/test_shader_store.pyalso with g++ (libstdc++), for the library's branch of the folder's name. The renderer builds alone (RX_VK_ONLY). The Linux launcher's filter run under a shell: it dropsLONGSHOT_Xand keepsLONGSHOT_SHADER_CACHE.Not tested:
host/hos_main.c's--shader-cacheis not compiled yet. It needs the lift's runtime headers, so a whole build: the first run under Must be tested is one. It is two short blocks, as--compile-shaders' beside them.flockpath of the lock, andtests/test_longshot_launch.py(it runs only there).Must be tested:
LONGSHOT_SHADER_CACHE=<store>set for the build (its preparation fills the store), then a second game folder of the same commit with the same switch: expecting the second'slongshot_log.txtto sayvk: shader store: stamp <hex>: the shader caches are in <store>\<hex>\and its first start to prepare nothing, and the store to hold one stamp folder. Then start both folders at once: the second saysanother Longshot uses the store'sand runs from its owncache/. Then start a folder that has a fullcache/from main with the switch set: its files move into the store and it does not prepare again. Then a build of another translator into the second folder: its start sayslet gofor the old stamp only if the first folder no longer uses it, and with--shader-cache-keepboth stay. Each reaching play from a save.<store>/<stamp>/.lockheld by the first (theflocklock), the second running from its owncache/; and the AppImage's Play button (longshot-launch) starting the game with the switch still set.Risks
What could break: with the switch set, a folder's old stamp is deleted when it moves to another and no other existing folder still uses it: a folder whose record or marker went missing could let go of a stamp another build still wanted (that build then prepares again; the shared lists stay). With the switch set, a game that read or wrote a shader cache file in the wrong place would prepare again, or lose what it compiled in play; the call sites are all checked by text, and unset nothing moves. Two runs of one stamp could clash: each part is held for the process's life, and the second run uses its own
cache/. A store on another drive than the game folder makes the first take-in a copy (several GB) instead of a rename. The store grows by a whole set of caches for each translation until something lets the unused ones go: the launcher PR for players,scripts/shader_store.py prunefor development stores.Turning it off: unset
LONGSHOT_SHADER_CACHE, or start with--shader-cache 0: the game folder'scache/again. A store's files stay in the store until pruned.Compatibility: nothing changes for players until the launcher sets the switch: unset, every file is where it was, and nothing takes the stamp sooner than before. With it set, a game folder's existing caches of the same stamp move into the store at its first start. Saves, the options file and the vk ABI are unchanged.
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
b34e61c30b0e8569be74Rebased onto main
9e6f231d(past #729, which added options_path beside the store's paths in vk_paths.h: both kept). New: a stamp's folder is named by the stamp and the toolchain (folder_key: the compiler's version and the C++ library's), so caches never cross a toolchain in the store; HumanBean's point on LongshotLauncher#78 about libstdc++ (the stamp carries libc++'s and Microsoft's library version, not libstdc++'s) applies here too. The stamp itself is unchanged, so nobody's caches go. The record now says both (stamp=, translation=). Full suite again on this branch and main: no new failure; the store's check passes with clang and g++.0e8569be74a45e8c89d0Pushed
a45e8c89(rebased onto maina00c485c): an update in place, in the store. Each game folder registers in the stamp it uses (/users/) and records it; when a folder starts under another stamp, its old one is renamed aside and deleted in the background unless another game folder still on it uses it, a running game holds it, or LONGSHOT_SHADER_CACHE_KEEP=1 / --shader-cache-keep asks to keep both (an A/B). So a folder holds one set of caches at a time, as without the store; the old files are not reused under the new stamp (another toolchain keeps the stamp, not the records' layout). Also fixed: host/options_path.c (#729) took --shader-cache's value for the image; it now skips both arguments, with a case in test_options_path.py that fails before. scripts/shader_store.py prune now reads the stamps' users itself. Renderer builds; full suite: no failure main doesn't have. Still WIP for the whole build (the host change) and the runs in the game under Must be tested.a45e8c89d0fea52a0be3Rebased onto
main@7bacf846(no conflicts); pushed asfea52a0b. No tests were run here; the test boxes stay yours.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.