WIP: fix(macos): hold MoltenVK's giant compiles behind the game until its first frame, at a lower QoS class #665
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!665
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/title-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: macOS, Apple silicon (a start on the rebased stack); Windows and Linux, any GPU (build and start)
Why
On a Mac the game's first frame could wait minutes after the precompile had let it start. Metal's compiler service has a
fixed number of connections (16 on an M4 Max), and the precompile's workers kept giant compute compiles running on all of
them behind the game. The driver compiles a small blit program inside the game thread's first submit, and that request had
to wait for a giant on its connection to end: in start 17 the game thread sat in
_dispatch_sync_f_slowbehind 16 giantcompiles, and the first frame came only after the compiler service crashed under them, about 3 minutes after the hand-off.
A giant whose compile failed that way was also compiled again at once among the other giants, where it failed again. This PR
keeps the precompile's giants from starting behind the game until the game has shown its first frame, builds the giants and
everything behind the game at a lower QoS class than the game's threads, and tries a failed giant again alone. 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 4e472987, 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 precompile's hand-off.
Expected: the first frame a few seconds after the hand-off.
Actual: start 17: the first frame came after the burst in which every giant still in flight failed
(
XPC_ERROR_CONNECTION_INTERRUPTED … after multiple retries), about 188-198 s after the hand-off; a sample at 08:25 had thegame thread in
tryBoost→_dispatch_sync_f_slowand 16 compiler connection threads each in a synchronous send(
reports/2026-11-14-mac-startup-17.md; analysis inreports/2026-11-14-title-stall.md§1-§2).What changed
Under
compiler_service(MoltenVK only, #652), behind the game no compile of more thanRX_VK_GIANT_WORDSstarts until thegame has shown
RX_VK_GIANT_HOLD_PRESENTS(1) frames since the hand-off, or forRX_VK_GIANT_HOLD_S(120) s at most; thegiants in flight at the hand-off finish, the held ones are deferred and built after it, never dropped. The precompile's workers
build every giant, and everything behind the game, at QoS class utility (
RX_VK_COMPILE_QOS); the rest, which the titlewaits for, keep the thread's own class. A giant compile that fails is not tried again at once among the other giants: its place
is given back and it is compiled once more alone, with a new pipeline cache, once no other giant is in flight and none starts
meanwhile. Every other driver is unchanged.
gpu/vk/precompile_handoff.h(no Vulkan):giant_hold_presents/giant_hold_s,Giants::hold/release/held(the holdin the gate),
compile_qosandqos_for(which class a run is built at),ThreadQos(a worker's class, changed only when itmust),
Retry(Now, Later, Fresh) andGiants::solo_ask/solo/solo_end/solo_cancel(one giant alone, in turn).gpu/vk/vk_draw.cppprecompile_run: the hold set at the hand-off and released by the presents counter or the time, oneline when the giants go on;
behind_workertakes the class for each run (compile_qos_apply:pthread_set_qos_class_self_npon macOS, nothing elsewhere); its compute arm marks a giant's compile
Retry::Laterand, when it failed, callscompute_alone;create_watched's second try honourscompile_retry.docs/development.md: rows forRX_VK_GIANT_HOLD_PRESENTS/RX_VK_GIANT_HOLD_SandRX_VK_COMPILE_QOS; theRX_VK_COMPILER_SERVICErow names the retry alone.tests/precompile_handoff_test.cpp(the hold behind the game, the QoS choice, one giant alone at a time) andtests/test_precompile_handoff.py(the wiring; on macOSvk_draw.cpp's owncompile_qos_applyon a real thread, whichshows that MoltenVK's way of asking for a compile, a block dispatched to the default global queue, runs at the worker's
class).
Measurements
Verdict: works as designed in the game, but its own gain is not measured: on the integration builds that carry it the hold
held the giants until the first frame (start 21: 143 deferred, released 27.0 s after the game started, 1,183 frames shown;
start 24: 212 deferred, released after 46.2 s) and the first frame came 4.5-5.0 s after the hand-off (starts 21-24). Starts 18
and 19, with #664's bound but without this PR, also had their first frame 4.6 and 4.5 s after the hand-off, and the
game-free probe TS-02 did not reproduce the wait (its blit program came from Metal's cache in 0.77 ms under 16 compiles). So
this PR removes the wait's cause (no giant starts while the game has shown nothing) without a measured before/after of its
own. No giant failed in starts 21-24, so the retry alone did not run.
Baseline: main does not run on macOS yet, so the columns are the macOS port's integration builds, arm64, MoltenVK 1.4.2, start
mode. Before: 4e472987 (start 17: no bound, no hold). After: 09f9a171 (start 21, Integration 28, which carries this topic and
#666). One run each.
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 hand-off line, the hold lines and the first
vk: present texture … renderedline, one run each; theprobe TS-02 (
reports/TitleStall-files/ts02/run-01-arm64/,summary.json53fddf57…)How to reproduce: a start of a macOS build from a seeded cache;
RX_VK_GIANT_HOLD_PRESENTS=0turns the hold offRun reports:
reports/2026-11-14-mac-startup-17.md,-18.md,-19.md,-21.md,-22.md,-24.md; the analysis andTS-01/TS-02:
reports/2026-11-14-title-stall.md.Flags and settings
Three new switches, read only with
RX_VK_COMPILER_SERVICE(MoltenVK); nothing changes elsewhere.RX_VK_GIANT_HOLD_PRESENTS0: no holdRX_VK_GIANT_HOLD_SRX_VK_COMPILE_QOSutility,backgroundordefault: the class giants and work behind the game are built atdefaultTesting
Tested: on macOS 26.7.1, Apple M4 Max, at this head (main
6c9edffeplus #650, #651, #652, #664 and this PR):tests/test_precompile_handoff.py --arch arm64and--arch x86_64(the fixtures run under Rosetta): pass on both (the hold,the QoS choice and fixture, the retry alone, and every earlier check). On this branch's base (#664
20367871) withthe test files copied in (sha256 in
ts-test-files.sha256): fails on both, the fixture does not build and the source checksname what is missing (
precompile_run lacks: … giant_hold_presents …,… compile_qos …)(
reports/PubWave-files/PubLate1/tsls-proof-01/ts-*).tests/test_warm_handoff.py --arch arm64still passes (tsls-proof-01/ts-warm-arm64).299 checks on this branch), through
tools/ls-suite.py: 279 pass, 20 fail. 19 are the same 19 as at #652's head (16 also fail on main6c9edffeon macOS;3 are skipped on macOS in a full run and fail only because named); the 20th is
test_gpu_hang_watch("exit 3, 3.5 s" againstits 3.4 s bound, at load 44-50), the suite's known load-sensitive timing test, which reads no file this PR changes
(
title-stall-related-01/,title-stall-related-01-compare.txt).gpu/vk/vk_draw.cppcompiled whole for arm64 and x86_64 with main's renderer flags (with #476's and #492's macOS compile piecesin a scratch tree): both objects made, exit 0 (
reports/PubWave-files/PubLate1/compile/title-stall/).unifdef -U__APPLE__, base against head):precompile_handoff.handvk_draw.cppdiffer: the header's newpieces; in
vk_draw.cppcompile_qos_apply's off-Apple body ((void)q;), the thread-localcompile_retryandcompile_failed_later, andcreate_watched's second try and line readingcompile_retry, which isNowthere (onlybehind_worker, undercompiler_service, sets another), socreate_watched(reached off Apple only withRX_VK_SLOW_COMPILE_Sset) tries again and logs as before (reports/PubWave-files/PubLate1/offapple-title-stall.txt).Not tested: the game at this head (main does not run on macOS without the other macOS PRs); the retry alone in the game (no
giant failed in starts 21-24); the QoS class's effect in the game against
RX_VK_COMPILE_QOS=default; Windows and Linux (thenew code runs only under
compiler_service, off there; it is compiled there): not built on Windows or Linux; its test requestcovers it.
Must be tested:
giant compiles go on … after the game startedafter the first frame, and the first frame a few seconds after the hand-off (byhand: the parent's GPU slot).
d429f0f0, it carries #650, #651, #652 and #664) and main6c9edffethe same way and start each to the title, expecting no
behind the game, no giant compile starts until …line (the hold, theQoS class and the retry alone are compiled there and not reached), and every other line to match main's (by hand: test
request T-XXXX).
Risks
What could break: on MoltenVK the giants behind the game wait for the first frame (at most 120 s), so they finish later; a
dispatch that needs one meanwhile compiles it itself, as before. The worker threads change QoS class (macOS only), which Metal
and the scheduler honour; the game's threads keep theirs. A giant that fails waits for its turn alone. Other drivers: the
header's code and the
compile_retrycheck increate_watched, not reached there.Turning it off:
RX_VK_GIANT_HOLD_PRESENTS=0(no hold),RX_VK_COMPILE_QOS=default(no class change),RX_VK_COMPILER_SERVICE=0(all of #652's path), or revert the PR.Compatibility: Nothing changes for players on Windows and Linux: no shader stamp, cache format or
MKWVK_API_VERSIONchange.
Reviewer notes
c291d0c6..d429f0f0(3 commits; in git's notation20367871..d429f0f0): the hold and the QoS class, their docs rows,and the retry alone.
Giants, the bound this hold sits in), and through it on #652, #650 and #651. The branch is#664's head
20367871plus this PR's commits.-xfrommacos/title-stall(4f319a0d 36e04c5e b6069bba), made on theintegration branch. Conflicts in the pick:
gpu/vk/vk_draw.cpp: #652'scompiles_watchedand this PR'scompile_qos_applyadded at the same place, both kept;behind_worker's compute arm: #652's adaptation to #252 (quick,precompile_compute_quick) and this PR'scompile_retry/compute_alone, combined (the quick flag is set before the compile, the retry alone after a failure).tests/test_precompile_handoff.py: #652'scached_*checks (main'screate_cachedline) kept, with this PR'scompile_qos_applychecks.docs/development.md: the rows take main'scache/longshot_pipelines.bin.slow(#321) with this PR's text; the integrationbranch's SPIR-V profile rows (a later macOS PR) are not here.
git merge-treeof this head: no conflict with #423, #427, #431, #438, #476, #479, #480, #490, #491, #492;gpu/vk/vk_main.cppwith #428, #498 and #653, and
vk_main.cpp/vk_progcache.cpp/vk_shader.h/vk_vendor.hwith #646: the same conflicts as#652's own head, none from this PR's lines (
reports/PubWave-files/PubLate1/merge-check-title-stall.txt).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 the macOS PRs this one is built with,bfe5c752(metal-cache2 + giant-mem-gate, which carry this PR): 544 passed, 32 failed, 17 skipped against main 6c9edffe's 531/32/17 on this Mac; the 32 failures are main's own, nothing that passes on main fails, 13 new tests pass;reports/PubWave-files/waveC1/.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.7707c634) under the precompile hand-off f9f166f7cbd429f0f08d40edc9da9540edc9da95a7e966ec87Folded into #849 (fix(macos): MoltenVK's precompile and pipelines on Metal)
Pull request closed