WIP: fix(macos): let MoltenVK's precompile go on behind the title and name slow compiles #652
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!652
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/precompile-stall"
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: fix · Risk: medium · Tested on: macOS 26.7.1, Apple M4 Max (MoltenVK 1.4.2), arm64 and x86_64 under Rosetta · Needs: Windows, Linux (build and start)
Why
On a Mac the title never came: the start's precompile (every noted pipeline and compute program, about 42,000 items) sat for
minutes with nothing finishing, because Metal's compiler service takes minutes over the game's largest compute programs (one
took 6.5 minutes) and every precompile worker held one of them. The rule that lets the game start while the last stragglers
build behind it waits until every item has been handed out, and with 37,600 of 42,234 still to hand out it could never apply
(run #12: stalled at 4,600 for 160 s and at 7,698 for 80 s, no title). Nothing in the log said which compile was holding the
start, and a compile that failed when the compiler service crashed was kept failed in its pipeline cache. This PR lets the
title start on MoltenVK while the rest goes on behind it, names slow compiles, and tries a failed one once more. It is part of
the macOS port. See #420.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK 1.4.2 (main does not run on macOS yet: seen on the macOS port's integration
build a7711f3c, which carries the open macOS PRs)
Settings and switches: defaults (start mode), the program cache carried from earlier starts
Steps: start the game and wait for the title after the shader preparation.
Expected: the title comes once the precompile has built what it can, the slowest compiles going on behind it.
Actual: the precompile stalls for minutes with all 16 workers inside the compiler service, and the title never comes
(run #12,
reports/2026-11-14-mac-startup-12.md; analysis inreports/2026-11-14-precompile-stall.md§1).What changed
On MoltenVK (a new vendor fix,
compiler_service), the precompile hands work out in an order made for Metal's compilerservice, lets the game start once nothing has finished for
RX_VK_PRECOMPILE_STALL_S(30 s) whatever is left, or once onlycompiles older than
RX_VK_SLOW_COMPILE_S(10 s) remain, and builds the rest behind the game on a quarter of the workers,every item still built. Every compile past the slow time is named while it runs and when it ends, and a compile that fails
with
VK_ERROR_INITIALIZATION_FAILEDis tried once more with a new pipeline cache. Every other driver keeps the precompile'sworker, its wait rule and its log line as they are.
gpu/vk/precompile_handoff.h(new, no Vulkan):due(the classic rule and the compiler service's),wait(the precompile'swait loop, moved, with the rule as a parameter),
Slots(what each worker has in hand, since when),Behind(onlycapworkers take new items behind the game; the last out says so),
behind_workers(a quarter, 2 to 4),order(notedpipelines first, then mesh fallback and compute programs by SPIR-V size, smallest first, what the slow list names last),
Watch(names a running compile at the slow time and each doubling),slow_line/slow_parse.gpu/vk/vk_draw.cppprecompile_run: the classicworkerunchanged;behind_workerundercompiler_service(its order,its slot, a compute program taken through
compute_stateas a dispatch takes it, a noted pipeline made behind the game putwhere a draw's lookup takes it,
pipeline_keep_ready, never a null one); the wait throughprecompile_handoff::wait; theworker caches (#650) merged by the last worker behind the game, when none compiles with them.
create_watched(aroundcreate_cached, which is unchanged):cached_graphicsandcached_computetake it inside #252'sIntel gate where compiles are watched (
compiles_watched:compiler_service, orRX_VK_SLOW_COMPILE_Sset) and keep main'screate_cachedline everywhere else. It adds the slow-compile watch (compile_watch_begin/_end, a watcher thread, the slowlist
cache/longshot_pipelines.bin.slow) andcompile_again(a new pipeline cache, one more compile, the line… compiled once more with a new pipeline cache: made|not made). An ask (FAIL_ON_PIPELINE_COMPILE_REQUIRED) is neither watched nortried again.
Taggedscope at the eight sites that compile (the precompile's and the warm's notedpipelines, the mesh fallback's compute stages,
prepare_run's compute warm, the warm's and the dispatch's compute builds);Stage::words(gpu/vk/vk_shader.h), the SPIR-V size, noted where the program cache sees SPIR-V (gpu/vk/vk_progcache.cpp).gpu/vk/vk_vendor.hcompiler_service(MoltenVK only,RX_VK_COMPILER_SERVICE);docs/development.mdrows forRX_VK_PRECOMPILE_STALL_S(undocumented until now),RX_VK_COMPILER_SERVICEandRX_VK_SLOW_COMPILE_S.tests/test_precompile_handoff.py+tests/precompile_handoff_test.cpp(new): the header with workers whose createblocks on the giants (run #12's shape: the classic rule does not hand off, the compiler service's does after 1-2.5 s, every
item built once, at most the share behind the game, the stragglers named), the watch, the slow list and the order;
vk_draw.cpp's owncreate_watchedandcompile_againagainst a made-up driver that keeps a failed library in its cache asMoltenVK does;
vk_vendor.h; and the source's wiring.Measurements
Verdict: fixes the stall: with this change the title and the menu came (run #17: handed off at 1,308 s with 42,043 of
42,135 done, 16 giants in hand, the rest behind the game on 4 workers; later integration runs #18, #19 and #21 handed off after
626-694 s, the first present 4.5-5 s later), where run #12 without it stalled with no title. The slow-compile lines name the
giants (run #14: 57 compiles named, 44 of them finished after 12.9-23.4 s, 13 still running at the quit). Measured on
integration builds before the rebase; the hand-off itself is unchanged by it (the adaptation adds #252's quick builds, which
apply only on Intel's one-at-a-time builds).
Baseline: main does not run on macOS yet, so both columns are the macOS port's integration builds, arm64, MoltenVK 1.4.2, start
mode, the program cache carried. Before: a7711f3c (run #12), without the hand-off. After: 4e472987 (run #17, integration 23 plus
this topic). One run each; run #17's timing is load-affected (full suites held the suite slot throughout).
Hardware: macOS 26.7.1, Apple M4 Max (40-core GPU), MoltenVK 1.4.2, 16-core CPU, 64 GB
Scene: a start to the title, windowless, defaults
Method: the game log's
vk: precompile:andvk: start-up:lines and the bridge's menu-ready, one run eachHow to reproduce: a start on a macOS build with
RX_VK_COMPILER_SERVICE=0(as before) against unsetin hand when the game startedlines, 15behind the game … finished(7 made)Run reports:
reports/2026-11-14-mac-startup-12.md,-14.md,-17.md,-18.md,-19.md,-21.md.Flags and settings
One new switch that turns the change off, and two switches with new defaults on MoltenVK; nothing changes elsewhere.
RX_VK_COMPILER_SERVICE=0: the precompile, its wait and its compiles as on every other driver;=1: the hand-off, the order, the watch and the retry on any driver=1=0only to compareRX_VK_SLOW_COMPILE_S0: nonecompiler_service, none elsewhereRX_VK_PRECOMPILE_STALL_Scompiler_servicewhatever is leftTesting
Tested: on macOS 26.7.1, Apple M4 Max, at this head (main
6c9edffeplus #650, #651 and this PR):tests/test_precompile_handoff.py --arch arm64and--arch x86_64(the fixtures run under Rosetta): pass on both (thehand-off fixture and
compile_again: all checks). On main6c9edffewith the test files copied in: fails on both (noprecompile_handoff.h, nocreate_watched/compile_again, 35 source checks name what is missing). On this branch's base(#650 with #651 merged,
f9f166f7) with the test files: fails on both, the same way.reports/PubWave-files/PubPrep/proof-03/(
p37-head-ph-*,p37-base-ph-*,p37-pre-ph-*).test_pcache_fetch_lock(#190, both arches) andtest_intel_compiler_gate(#252),test_fallback_pipeline_state(it compilesbuild_graphicscut from the source),test_thread_stack(both arches) andtest_worker_caches(both arches).reports/PubWave-files/PubPrep/proof-03/.tools/ls-suite.py: 279 pass, 19 fail; 16 of them fail on main6c9edffeon macOS too (reports/Baseline-files/main-6c9edffe/),and the other 3 (
test_precompile_compute_guard,test_shader_module_guard: "Windows structured exceptions required";test_present_aspect_vulkan: no Vulkan library to link on this Mac) are skipped on macOS in a full run and fail only becausethey were named.
reports/PubWave-files/PubPrep/p37-related-03/,p37-related-03-compare.txt.vk_draw.cpp,vk_main.cppandvk_progcache.cppcompiled whole for arm64 and x86_64 with the renderer's flags, with themacOS compile pieces of #476, #490 and #492 applied: 6 of 6.
reports/PubWave-files/PubPrep/p37-tu-stack-03/.git merge-treeof this head with each open macOS PR's head (#431, #437, #438, #476, #479, #480, #490, #491, #492): noconflict.
Not tested: the game at this head (main does not run on macOS without the other macOS PRs), the hand-off on x86_64 under
Rosetta in the game, a compile failure that the second try saves (run #14's two retries were both
not made (-3): bracketnesting in Metal, fixed elsewhere), and Windows and Linux (no change in behaviour there:
compiler_serviceis off, the classicworker and its rule are unchanged; not built or run on them: its test request covers the build and a start).
Must be tested:
RX_VK_COMPILER_SERVICE=0against unset, expecting the hand-off line,
in hand when the game startedlines and the title with it, and the old stallwithout it (by hand: the parent's GPU slot).
7ef7cff9, it carries #650 and #651) and main6c9edffethe same way and starteach to the title, expecting the same start-up lines as main, no
in this order for Metal's compiler serviceline, and theprecompile finishing before the title as on main (by hand: test request T-XXXX)
Risks
What could break: on MoltenVK the title can come while giants still build: a draw or dispatch that needs one is left out
until it is ready, as with play's own builds; a behind-the-game compile holds a worker until the process ends (a quit ends the
process without joining them). With
RX_VK_PCACHE_LOAD=1on a Mac a play-cache save waits for compiles counted in itsgeneration (see
reports/2026-11-14-precompile-stall.md, Addendum). Other drivers: aTaggedscope (a thread-local set) ateach compile site and
Stage::wordswritten where the program cache reads SPIR-V; the precompile's wait loop is moved intoprecompile_handoff::waitwith the same expression in the same order.Turning it off:
RX_VK_COMPILER_SERVICE=0, or revert the PR.Compatibility: Nothing changes for players on Windows and Linux: no shader stamp (
Stage::wordsis not in the translatorstamp, which takes
sizeof(Program)), cache format orMKWVK_API_VERSIONchange. On macOS a new small file,cache/longshot_pipelines.bin.slow, beside the notes.Reviewer notes
89606310..7ef7cff9(5 commits; in git's notationf9f166f7..7ef7cff9): the hand-off and the retry, the test'smessages on a tree without them, the docs rows, the graphics names at the callers, and the adaptation to main (
7ef7cff9).worker_caches_merge,worker_cache_calls,pcache_files_unused) and #651(renderer threads with 16 MiB stacks:
thread_stack::thread, which the watcher thread uses). The branch is #650's head(
078c9a7a) with #651's head (7707c634) merged (f9f166f7): 4 blocks ingpu/vk/vk_draw.cpp(the include block and thethread lines of
prepare_run,precompile_at_createandprecompile_run), each side's lines kept.-xfrommacos/precompile-stall(77d4971c f9a179c0 194a230d 528784dc),made on the integration branch. Conflicts in the pick:
gpu/vk/vk_draw.cppcached_graphics/cached_compute: #252 (57f4eaec) put Intel's gate and #190 (f41a5c59)guarded_*_heldthere; kept, withcreate_watchedwhere compiles are watched (the adaptation commit keeps main'screate_cachedline for every other driver).gpu/vk/vk_draw.cppprepare_run's compute warm: #252's gate and #650's rework (create_cachedunder the gate); thecaches' path goes through
create_watchedthere where compiles are watched, named.gpu/vk/vk_draw.cppthe dispatch's inline build: the integration branch's nameplain(#663's rename) ispon main.vk_progcache.cpp(3),vk_shader.h,vk_vendor.h(3): the integration branch's context held #643'sset_for_tablesand#644's
array_length/set_for_lengths; only this PR'swordsandcompiler_servicelines were kept.7ef7cff9:cached_*and the warm keep main's (and #650's)create_cachedlinesand go through
create_watchedonly where compiles are watched, so every other driver's pipelines are made as on main andmain's own source checks (
test_intel_compiler_gate,test_pcache_fetch_lock) read what they read there;behind_workerbuilds compute pipelines as main's classic worker does since #252 (Intel's quick builds,
optimize_owed); the test's sourcechecks follow main's spellings; the slow list is in
cache/since #321 (76699aad).tools/ls-run.py's carry list shouldcarry
cache/longshot_pipelines.bin.slow.vk_vendor.h,vk_progcache.cpp,vk_shader.h: each side's fields and lines next to these),#663 (renames the dispatch's
ptoplain: this PR'sTaggedline follows it), #648 (vk_progcache.cpp), #423 (log linesbeside the precompile's: its
RX_VK_FAIL_REASONSsummary, folded into #423 by the owner).(
git merge-tree).Issues
See #420
Checklist
Everyone
type(scope): description) of the status line's type.main(maine090cc48).python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (On the wave's merge of every macOS PR built with this one,453eb678: 553 passed, 32 failed, 18 skipped against main 6c9edffe's 531/32/17 on this Mac; the failures are main's own excepttest_split_ub_flush, which #658's tree at the time broke and its head fixes;test_collect_widenow passes; the new tests pass,test_precompile_handoffamong them, andtest_darwin_audio_service, Darwin-only, is skipped by the runner;reports/PubWave-files/waveA/.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here.7707c634) under the precompile hand-off f9f166f7cb7ef7cff97702cb2bc35f02cb2bc35fba5aa17684Folded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)
Pull request closed