Repository navigation
Integer reads of game uniforms are volatile loads: bare, they made Metal's backend take minutes and run out of memory on Switch Sports Resort's pipelines - #2271
Conversation
…d (dmsh_rewrite.flag_reads): bare, such reads made Metal's backend take minutes and gigabytes for Nintendo Switch Sports Resort's material pipelines and ran MTLCompilerService out of memory in the prebuild
seedds#2273 (black outfits in menus); Miis with seedds#2274 and the player's own files; golf's far ground right with the seedds#2040 series
…s read by a volatile load per test, so Metal's front end folds no chain of those tests into a switch, which Apple's GPU compiler took minutes to hours and gigabytes over (PR #2372): since the host bounds fixed-offset uniform reads once a binding (e45694b, chain b7249b1c2b66) the reads are plain, and the translator writes a GLSL switch on a word as one test per case with each case nested in the next (bool _5 = as_type<int>((*spvDescriptorSet0.fp_c5)._m0[16].w) != 102); SimplifyCFG (at -O1 too) folds such a chain into an LLVM switch whose cases jump into nested blocks past their joins, and applegpu-nt then takes the graph for hours. Nintendo Switch Sports Resort's (04008E001F098000) pixel shader 692008b305ce264f went from no switch in its IR to 42 and 7c2becb054a53765 from 6 to 66; 2265ca4db20439bf + 692008b305ce264f alone (metal-tt 32023.922, applegpu_g14s, M2 Max): 7.4 s / 0.25 GB on chain 592225da745b, stopped at 600 s / 13.4 GB on b7249b1c2b66, 6.3 s / 0.21 GB with this; the Mac app's archive of 11,075 pipelines built in 4 min 45 s (the contributor's numbers, with a separate archive change). dmsh_rewrite.uniform_tests reads each such test through dm_uword, a volatile load of that word, which the front end can neither merge with the next test's load nor move out of its block; a word tested once, a test against a variable and an ordering test are left. Only game slots (c3 and up): the driver's banks are left as written, since dmsh_entry.vs_base_instance looks for the read of c0 row 100 by its text after this rewrite and the host fills those banks. dmsh_build.py calls it after the other MSL rewrites (before blend_zero), and on the geometry stage and its reader. A workaround for Apple's compiler (a platform limit), not a fix of the translator; turning the switches back into compares (lowerswitch) does not help, the graph is the cost. Chosen over PR #2271's volatile load for every integer fixed-offset read of a game slot, which on older-chain MSL would change 32 to 56% of shaders (Ocarina of Time, Mario Kart World, Metroid Prime Remastered samples) where this changes under 1% and at most 0.15% of fixed reads, keeping e45694b's GPU saving. Chain change: DMSH_CHAIN 8d9afacc48e2; the lock is unchanged. New reference entry (Tests of one uniform word folded into switches), symptom index regenerated. Checked: test_dmsh_uniform_tests (the forms rewritten and left, the driver's banks left, and with Xcode the plain shader's IR has a switch with dmsh_chain.MAC_METAL and ios_metal.METAL and the rewritten one none); dm/tools/tests (1,496) and dm/gfx/tests (544) with the tool venv; test_docs, test_nsp_chain, test_mac_metal; lint_python.py, nothing new. regress.py on NieR:Automata (moderate reach, one light game with a shader of this form): chain diff 1,225/1,225 built on both sides, one shader's MSL changed (14206a699c7c386c, used on the route); check --quick boots and runs the route on both, no new red line, the 100 s screenshot differs by RGB 6.16 at a different animation moment (61 frames apart), within NieR's run-to-run noise there. Not run: the GPU cost in a frame, PR #2271's six pairs on this chain, Switch Sports Resort (not on this Mac), the iPhone Co-authored-by: bymaxmarc <354601562+bymaxmarc@users.noreply.github.com>
|
Thanks, this found the same cause as #2372, which landed on main as dfc5a15. That change makes a uniform word volatile only where it is tested for equality against constants more than once, rather than every integer fixed-offset read: on older-chain MSL your form would change 32 to 56% of shaders, and #2372's under 1%, which keeps more of #2066's GPU saving. Before closing this: could you rerun your six pairs ( |
|
Reran the six pairs on main 6815810 (with #2372, dfc5a15): the 11 shaders translated from Switch Sports Resort's packages on main's chain, one render pipeline per pair from the build's
(front + back.) None is slow on main. The pixel shaders carry #2372's Not run: the game's whole prebuild on main. |
…fset is a volatile load (dmsh_rewrite.flag_reads): bare, such reads made Metal's backend take minutes and gigabytes for Nintendo Switch Sports Resort's m # Conflicts: # dm/gfx/nvn/dmsh_chain.h # dm/gfx/tools/dmsh_rewrite.py
…rseded by seedds#2372, on main as dfc5a15
What and why
Since e45694b (#2066), a game uniform read at a fixed offset is a plain load. On Nintendo Switch Sports Resort (
04008E001F098000, v65536) that makes Metal's backend take minutes and gigabytes for some material pipelines. The prebuild compiles them side by side andMTLCompilerServiceruns out of memory:[RED] pipeline prebuild: the base of 0eb245e18bea1bac + fbe6bb186641245c failed: Compilation failed due to an interrupted connection: XPC_ERROR_CONNECTION_INTERRUPTED, 140 failed pairs in one start.MTLCompilerServicecrashes in one evening, each anoperator newfailure underllvm::CloneBasicBlock.[RED] pipeline archive: writing …/p00130.mtl4a failed: Failed to generate machO:warning: units in '…/com.apple.gpuarchiver/…/air64_v29' are already packed. Two files (about 3,400 bases) were lost in one start, so the next start built them again. The game compiled pipelines for minutes at every launch.Cause. These pixel shaders read integer flags from a game uniform (
as_type<int>((*spvDescriptorSet0.fp_c5)._m0[40].x), 455 such reads infbe6bb186641245c) and test them at each level of branches nested 36 deep. With the comparison gone, the backend takes every read of one word as one value, and its instruction selection blows up. I narrowed it on the shader's text:c5comparisons taken out: slow.c6taken out (read as floats): 0.6 s.Fix.
dmsh_rewrite.flag_reads(called afterselect_guardsindmsh_build.py) reads a game uniform (c3and up) that the shader takes as an integer at a fixed offset through a volatile pointer:Each read stays a load of its own, as the compared read was, with no size table and no comparison, so #2066's change stands. Float reads and the driver's banks (
c0toc2) are left alone: they did not slow the compile, anddmsh_fetchreads the float reads' text. Chain change:3695a7c258bb.Measured
One render pipeline per pair, built from the shader build's own
.metallibfiles withnewRenderPipelineStateWithDescriptor, the Metal function cache emptied before each. M4 Pro, macOS 27.2. Another build was running on the machine during these, so the main column is somewhat inflated; the first pair took 78.9 s on the idle machine.0eb245e18bea1bac+fbe6bb186641245c00a37c8963c861be+8bdcdee8e27bd27103e62a4a5e7ece89+0d57ffc025e0df8f03e62a4a5e7ece89+6bce362cc76ee22607275c719cb102f9+a0590cfb102488640fe3dadc985ff916+dea5bd6c61bfddd3These are the six pairs the game's prebuild logged as failed.
Checked
test_dmsh_flag_reads.py(new): the integer reads of game slots are wrapped; float reads, a driver bank and a computed offset are not; a shader with none is unchanged; the result compiles with the build's Metal flags.dm/gfx/testsPython tests pass (53 of 54 run this way;test_pso_archive.pyneeds its executable argument and was not run).test_docs.py,test_env_switches.py,test_architecture.pyand the symptom index pass.Not checked
The game's profile is in #2107.