Repository navigation
A uniform word tested against constants is read by a volatile load per test, so Metal's front end folds no switch that Apple's GPU compiler takes hours over - #2372
Closed
bymaxmarc wants to merge 1 commit into
Conversation
…er test, so the front end folds no switch for Apple's GPU compiler Since reads at fixed uniform offsets are plain again (e45694b: the host pads a short binding, chain b7249b1c2b66), the Metal front end's SimplifyCFG folds the translator's GLSL switches (one test of a uniform word per case, `bool _5 = as_type<int>((*spvDescriptorSet0.fp_c5)._m0[16].w) != 102;`) into LLVM switches whose cases jump into nested blocks past their joins. applegpu-nt then takes such a pixel shader for hours and tens of GB: Nintendo Switch Sports Resort's Mac app pipeline archive ran 15 h in its first batch on four pipelines (pixel 7c2becb054a53765 with three vertex shaders, 692008b305ce264f with 2265ca4db20439bf). - dmsh_rewrite.uniform_tests: a word tested for equality with an integer constant more than once is read through dm_uword, a volatile load of the word per test, so no chain is folded. A word tested once, a test against a variable and an ordering test are left as they are - dmsh_build.py calls it after the other rewrites (before blend_zero, which adds main0's last parameter and the entries that share its body), and on the geometry stage and its reader - test_dmsh_uniform_tests.py: the forms rewritten and left; with Xcode, the Mac and iPhone commands give the plain shader a switch and the rewritten one none (MAC_METAL is read from dmsh_build.py with dmsh_chain.MAC_MIN_FLAG evaluated) - docs/reference/shaders.md: Tests of one uniform word folded into switches; symptom index regenerated; chain header (d24f05cf9d78 -> a9bb9ce825a2 on main ccd834e) Measured alone with metal-tt 32023.922 (applegpu_g14s, M2 Max 32 GB), 2265ca4db20439bf + 692008b305ce264f: chain 592225da745b 7.4 s / 0.25 GB; b7249b1c2b66 killed at 600 s / 13.4 GB; with this 6.3 s / 0.21 GB. The four pipelines in one run 15.7 s / 1.07 GB; 672c0510444c7b53 + 8679aa3ec010eb7e 13.6 s, 38b608050e482e53 + 931f4ed1270b2a30 24.0 s (68 and 36 switches before). Bisected on 692008b305ce264f: the old vertex library with the new pixel library hangs, the new vertex with the old pixel 4.2 s; the old MSL without its fixed-offset bounds tests hangs, with them kept only on the 910 reads that feed an equality test 3.1 s; metal-opt -passes=lowerswitch on the new IR still hangs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 11, 2026
seedds
added a commit
that referenced
this pull request
Nov 12, 2026
…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>
Owner
|
Landed on main as dfc5a15 (you are credited as co-author), with three changes on top:
Checked here: your test and the tool/gfx suites; |
Kayyluhh
added a commit
to Kayyluhh/nxnative
that referenced
this pull request
Nov 14, 2026
…rseded by seedds#2372, on main as dfc5a15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Since e45694b (chain
b7249b1c2b66), a uniform read at a fixed offset is bounded once per binding by the host, and the per-read bounds tests left the shader. The translator writes a GLSL switch on a uniform word as Ryujinx's structurizer gives it: one test of the word per case, with each case nested in the next (bool _5 = as_type<int>((*spvDescriptorSet0.fp_c5)._m0[16].w) != 102;). With the reads plain, the Metal front end's SimplifyCFG (at-O1too) folds a chain of such tests into an LLVMswitchwhose cases jump into nested blocks past their joins.applegpu-ntthen takes such a pixel shader for hours and tens of gigabytes. Nintendo Switch Sports Resort's (04008E001F098000) Mac app pipeline archive ran 15 h in its first group on four pipelines: pixel7c2becb054a53765with three vertex shaders, and692008b305ce264fwith2265ca4db20439bf.692008b305ce264fwent from no switch in its IR to 42, and7c2becb054a53765from 6 to 66. The same slowness would holdPreparing pipelineson a device.The fix:
dmsh_rewrite.uniform_testsreads a word that the shader tests for equality with an integer constant more than once throughdm_uword, a volatile load of that word per test (as_type<int>(dm_uword((*spvDescriptorSet0.fp_c5)._m0[16], 3)) != 102). The front end can neither merge one test's load with the next nor move it out of its block, so no chain is folded. A uniform load per test costs less than the bounds test each read had before e45694b. A word tested once, a test against a variable and an ordering test are left as they are.dmsh_build.pycalls it after the other MSL rewrites (beforeblend_zero), and on the geometry stage and its reader. Turning the switches back into compares (metal-opt -passes=lowerswitch) does not help: the cost comes from the control-flow graph, not the instruction.This is a chain change:
dmsh_chain.hgoes fromd24f05cf9d78toa9bb9ce825a2, and every game's shaders are rebuilt once.dmsh_chain.lockis unchanged (no tool binary changed).Reference entry: Tests of one uniform word folded into switches, symptom index regenerated.
This PR and the special-register-0x84 PR (#2373) each change
dm/gfx/nvn/dmsh_chain.h, so whichever lands second needsdmsh_chain.py --headerrun again on top of the first.Checked
python3 dm/gfx/tests/test_dmsh_uniform_tests.py: 5 tests pass. They cover the forms rewritten and left, and with Xcode,dmsh_build.py'sMAC_METALandios_metal.py's command give the plain shader aswitchin its IR and the rewritten one none. The test readsMAC_METALfromdmsh_build.py, which on main namesdmsh_chain.MAC_MIN_FLAG, so it evaluates that name.python -m unittest discover -s dm/tools/tests: 1,469 tests OK, 27 skipped, as on main (includestest_nsp_chain.py, which checks the header).python -m unittest discover -s dm/gfx/tests -p 'test_*.py': 494 tests, the 489 that main has plus the 5 new ones. The same 10 modules fail to import on this branch and on main, because they load a game profile at import andDM_GAMEwas not set (test_gfpak,test_kpka,test_monolith,test_mt_framework,test_needle,test_pg_shader,test_pkz,test_shader_split_progress,test_snowdrop,test_sumo). No other failure.make -k -C dm/gfx/tests GAME=010015100B514000 -j8 run(M2 Max): exit 0, every check passed.metal-tt32023.922 (applegpu_g14s),2265ca4db20439bf+692008b305ce264f: chain592225da745b(before e45694b) 7.4 s / 0.25 GB;b7249b1c2b66stopped at 600 s / 13.4 GB; with this 6.3 s / 0.21 GB. The four pipelines in one run took 15.7 s / 1.07 GB. Bisected on692008b305ce264f: the old MSL without its fixed-offset bounds tests hangs, and with them kept only on the 910 reads that feed an equality test it takes 3.1 s. With this and the separate pipeline-archive PR, the whole archive (11,075 pipelines) built in 4 min 45 s on that branch.regress.py: not run. Only this game has a build on this Mac. The rewrite reaches every game whose shaders test a uniform word against constants more than once, so it most likely reaches the Switch 2 games and the large material shaders (Odyssey, Tears of the Kingdom). Their shaders were not rebuilt or compared here. A maintainer'sregress.py check --gameson those is the open check.a9bb9ce825a2) were not rebuilt for any game, and the GPU cost of the volatile loads was not measured in a frame.Docs
docs/reference/shaders.md, andpython3 dm/tools/docs_symptoms.pywas run (the symptom index)dmsh_chain.py --headerwas run anddm/gfx/nvn/dmsh_chain.his part of the change