Skip to content

Fold scalar unsigned extended products with non-power-of-two constants - #1741

Closed
nufegia wants to merge 1 commit into
seedds:mainfrom
nufegia:contrib/scalar-extended-multiply
Closed

nufegia wants to merge 1 commit into
seedds:mainfrom
nufegia:contrib/scalar-extended-multiply

Conversation

@nufegia

@nufegia nufegia commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What and why

SPIRV-Cross emits extended products through an optnone helper to work around a Metal mulhi compiler error. Literal multipliers also remain behind that optimization barrier. This small pass recognizes the unchanged helper and explicit scalar unsigned operands, forms an inline 64-bit product, and extracts the exact low/high 32-bit halves for non-power-of-two uint literals.

The original helper remains intact. Signed/vector products, dynamic multipliers, powers of two, and unfamiliar helper forms are unchanged. No game key selects the rewrite, and no rendering-quality setting is changed.

Related: #1663 already proposes a power-of-two high-word shift rewrite. This PR excludes powers of two and handles the complementary full-product case, with focused GPU correctness tests. That PR's current head was checked; no separate PR for this narrowed case was found.

Checked

Apple M1 Pro (16 GPU cores), macOS 27.2 (26B123), Xcode 27.2 (27B52); Ocarina of Time 0400486024DEC000, v131072. Comparison: main 51ecd693 / chain 094f939bfef9 versus this isolated change / chain 042a6036306a.

Three alternating baseline/candidate pairs through scripts/bench.py, title-movie, seconds 20–50 from an empty save; medians of the three runs:

Metric Baseline Candidate
Rendered FPS (range) 26.57 (26.53–26.60) 28.40 (28.27–28.40)
GPU frame p50, ms 37.51 35.01
Frame p99, ms 38.69 36.43
Screen presentations/s 23.17 27.13
Discarded presentations per 30 s, from bench.py 100 37

Rendered throughput improved 6.9%; paired gains were 7.0%, 6.3%, 6.9%. GPU frame p50 fell 6.7%. Screen presentations count distinct @pres_at_ms timestamps in the same window on a 144 Hz display; they are separate from rendered FPS. Presentation-interval p99 remained 76.39 ms: this does not fix frame pacing or reach 50 FPS.

Original NDVE enhancement, docked mode, game upscaler, no present upscaling. Both variants used their matching host, shader image, shader-map/derived tables and separate warmed device pipeline archives. Settings: DM_FOCUS_KEEP=1 DM_OPMODE=1 DM_UPSCALER=game DM_PRESENT_UPSCALE=off DM_PSO_PREBUILD=0 DM_PSO_BUNDLE=off; default display link and Metal HUD. All six 52-second launches had zero RED lines, focus pauses or display-link rescues. Startup preparation was 0.83–1.37 s, all 916 bases from the archive, before the measurement window. No builds ran during measurement. Warmup runs were excluded from formal timing.

Screenshots after the timing window were inspected; they show different movie/animation phases and are not a pixel-equivalence proof. This validates the title movie, not gameplay or other areas.

  • 393,216 integer products executed on the Mac GPU and checked against CPU uint64 results, including edge/overflow cases. The synthetic kernels also compile with the iPhone SDK; no iPhone device run.
  • Shader Python discovery: 427 tests, 130 skipped, passed. Final documentation and chain checks: 28 tests passed.
  • Full Metal graphics suite (make -C dm/gfx/tests GAME=0400486024DEC000 -j2 run) passed. Both matching Mac hosts built.
  • Python lint passed: 371 findings covered by the repository baseline, zero new findings or stale entries.
  • Python tools: 1,383 tests, 23 skipped, one failure. The same environment-switch documentation failure reproduces on clean 51ecd693: stale mentions of DM_NVN_CUT_FIRST, DM_NVN_RT_RECLAIM, DM_NVN_VC_WAYS, and undocumented DM_NVN_BC_ALIAS.
  • Both full shader builds: 42,848/42,848 successful. CHANGED is expected: 1,173 MSL sources contain the new helper/calls, reflection is unchanged, and 43 variants change with their source. No newly failing shaders or interface issues. There is no smoke.tsv for this profile, so the harness report covers the chain only; the benchmark launches provide runtime evidence.
  • Other games have not been run. The impact report identifies broad NVN reach and profiles without usage manifests; this is not broader game validation. iOS full-game compilation/device execution is also unchecked.

CI status

The Python lint job reports 376 findings against a 371-entry baseline (5 new), with errors in dm/tools/tests/test_patches.py. The upstream main run at 7d8dcdf8 has the identical error sequence and counts; this PR does not modify that file. Local lint on the fixed validation base passed with zero new findings. Remaining GitHub checks are pending; this is not an all-green CI claim.

Validation base

This round is fixed on 51ecd693, including its pixel-output-mask changes. The measurements above were repeated after integration; the shader optimization itself is unchanged. The measured and full-regression candidate is 588d967a; the final documentation amendment changes only the game note to record these results. Further upstream movement is outside this validation round.

Docs

  • General shader-reference entry and linked Ocarina note, including the reproducible route and full measurement conditions; symptom index regenerated.
  • dmsh_chain.h regenerated (042a6036306a).
  • No game files, keys or extracted data in the change or attachments.
Full shader regression report (Ocarina only)

regress.py check (full): CHANGED

Baseline 51ecd693 51ecd69, candidate HEAD 588d967a7a47; Apple M1 Pro, macOS 27.2, Xcode 27.2; 94.1 s

game status shaders (base → cand) newly failing newly building changed MSL interface (new / known) boot fps p50 frame p99 ms peak MB prepare s new RED
ocarina-of-time 0400486024DEC000 CHANGED 42848/42848 → 42848/42848 0 0 1173 0 / 0 – – – – – –

ocarina-of-time 0400486024DEC000: CHANGED

  • CHANGED: 1173 shaders changed (MSL 1173, reflection 0, variants 43)
    • changed shaders: 001fb0666a6c91d6, 002091f4cf5ca4d2, 00dcbbe2a3507874, 01212218400a3901, 0125a44d42f12ad0, 0125b61f162c7756, 012ecac37a2ae5fe, 014c9131f34c61ce, 018d3caa040d2fda, 01f9846e64cedcec, 0253bd17deceee79, 028bd0b187f7342c, 035fdfe919c75994, 03683384bd1c7892, 03725a808fb31b79, 039092a7689590d7, 04135bb812a0d66e, 0495766bdff48c2a, 049a5668c4608913, 054706c29f6aca44, 0572f0d80cd2f864, 05920ace83439f55, 05bac6d9be7750d0, 05bd904fcfe77b05, 05c11d009c9fba45, 05da669883cf2900, 05fb474f15d56286, 05fed053019c9b9c, 065f152f07e2976a, 0677d8b7144174aa, 069972cb65a4f8c8, 06da93be3fc952c6, 06f1ac387e8a00ec, 079bc469e3a5971b, 082e641683344d1d, 086e224e794cbfb1, 0893bcdc3943edf9, 089c8cfc0ccef309, 08eca37ef315de89, 08eea2183851ca98 …
    • time: chain 93.7 s

@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation shader chain The shader translation chain (DMSH_CHAIN): a change rebuilds every game's shaders game: 0400486024DEC000 A game labels Nov 8, 2026
@nufegia
nufegia force-pushed the contrib/scalar-extended-multiply branch from d5ab4cd to e7bf68a Compare November 8, 2026 02:44
@nufegia
nufegia marked this pull request as ready for review November 8, 2026 02:45
Recognize the unchanged SPIRV-Cross optnone helper and inline exact unsigned
64-bit products for explicit scalar uint operands with non-power-of-two
literal multipliers. Preserve the original helper and all other cases.

On M1 Pro, three paired Ocarina title-movie runs against 51ecd69 improve
rendered throughput from 26.57 to 28.40 FPS (+6.9%), with original NDVE
quality. GPU frame p50 falls from 37.51 to 35.01 ms. This does not establish
gameplay performance or fix presentation pacing.

Add focused rewrite guards, GPU integer-oracle tests, a benchmark route,
and reproduction/results documentation. Full Ocarina shader comparison:
42,848 built on each side; no new failures or interface issues.
@nufegia
nufegia force-pushed the contrib/scalar-extended-multiply branch from e7bf68a to f699e53 Compare November 8, 2026 02:46
Aznracer pushed a commit to Aznracer/Mighty-Green-Kokiri-Kid that referenced this pull request Nov 8, 2026
seedds added a commit that referenced this pull request Nov 8, 2026
… int calls of spvMulExtended reach inline overloads that take the halves of the 64-bit product, so the compiler folds Switch 2's constant divisions (Ocarina of Time: 348,587 calls, 98% by a power of two); on an M4 Pro the Kokiri Forest deck route ran at 59.1 fps instead of 54.0, GPU 15.55 -> 14.76 ms, and the title movie's GPU 12.96 -> 12.44 ms (chain 39417042cfe8; PR #1741)

PR #1741 rewrote the calls with a literal other than a power of two (5,679 of them):
the same title-movie gain, no change on the deck route (GPU 15.54 ms).

Co-authored-by: nufegia <11472001+nufegia@users.noreply.github.com>
@seedds

seedds commented Nov 8, 2026

Copy link
Copy Markdown
Owner

Thank you: the diagnosis, the GPU test and the title-movie route are in 37f076a, with you as co-author.

It landed as a different rewrite. Instead of rewriting call sites, mul_extended adds uint and int overloads beside SPIRV-Cross's spvMulExtended. They take the halves of the 64-bit product, with no mulhi, and overload resolution sends every scalar call to them. Vector calls keep the optnone template. The reason is the counts in Ocarina's MSL: 348,587 calls, all scalar uint by a literal, 98% of them by a power of two. This PR rewrote the other 5,679, and its regex missed 65 more (nested operands, literal first).

Measured on an M4 Pro, 3 runs per variant, medians, each variant with its own warmed archive and no red lines:

main this PR overloads
kokiri-deck fps 54.0 55.6 59.1
kokiri-deck GPU p50 15.55 ms 15.54 ms 14.76 ms
frames over 1.5 vsyncs 210 149 29
title-movie GPU p50 (all at 60 fps) 12.96 ms 12.48 ms 12.44 ms

So the title-movie gain is yours, and gameplay needed the power-of-two calls. Chain 39417042cfe8. Your M1 Pro title-movie numbers are kept in the Ocarina note. An M1 Pro run on main would be welcome, especially in gameplay.

@seedds seedds closed this Nov 8, 2026
Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
… int calls of spvMulExtended reach inline overloads that take the halves of the 64-bit product, so the compiler folds Switch 2's constant divisions (Ocarina of Time: 348,587 calls, 98% by a power of two); on an M4 Pro the Kokiri Forest deck route ran at 59.1 fps instead of 54.0, GPU 15.55 -> 14.76 ms, and the title movie's GPU 12.96 -> 12.44 ms (chain 39417042cfe8; PR seedds#1741)

PR seedds#1741 rewrote the calls with a literal other than a power of two (5,679 of them):
the same title-movie gain, no change on the deck route (GPU 15.54 ms).

Co-authored-by: nufegia <11472001+nufegia@users.noreply.github.com>
@nufegia

nufegia commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Following up on your M1 Pro gameplay test request. I ran Ocarina of Time v131072 on main at 1d377206 (shader chain d7576061facd), on an M1 Pro with a 16-core GPU and 16 GB RAM, macOS 27.2. I used scripts/bench.py on kokiri-deck, three 45 s runs from the same save with a warmed pipeline archive.

  • The three runs averaged 21.4, 16.2, and 14.6 FPS. The stationary segment held 20.6–21.1 FPS. The camera and walking segment varied from 9.9 to 21.6 FPS. Frame time p50 was about 50 ms and GPU time p50 about 38 ms.
  • Run 2 hit a 299.3 ms frame at 120.61 s, with 237.7 ms spent building one new pipeline. Run 3 hit 291.7 ms at 118.82 s, with 242.6 ms in submit+present, 65.4 ms of GPU time, and no new pipeline or shader library loads.

The partial shader build reported missing shaders during prewarm, with no such messages in the measured windows. The same timed inputs ended at different positions across runs. The display link fell back in all three runs, so the FPS above comes from engine frame records and the on-screen timing and drop counts are unreliable. The test covers one main revision, so it cannot isolate the effect of #1741.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400486024DEC000 A game nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) shader chain The shader translation chain (DMSH_CHAIN): a change rebuilds every game's shaders

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants