Repository navigation
Conversation
…ary, on the shared alpha test and logic operation compile (seedds#1036) border_emu wraps every sample through a sampler member in dm_bs, a branch the host turns on only for a sampler whose border colour Metal lacks. It almost never runs, but the GPU allocates registers for it in every pixel. ps_libraries now compiles <key>_b from the source without it (border_plain) and cuts the compile unit of the correcting source into <key>_bb (the shader's own entry points, correcting) and <key>_bx (alpha test and logic operation, which keep correcting). The host takes <key>_bb only while a sampler member of the pixel shader has a twin (border_key, bit 1 of pso_key.logic; bb_fn). On an M1, handheld: - Splatoon Raiders, after the first cutscene: c0ae76e0ca76b574 spilled 592 bytes a thread, now 0; the 236-draw 1920x1080 pass 20-21 -> 9.3 ms, the frame 31.6 -> 19.8 ms. - Donkey Kong Bananza, heavy area: 6f9fd9cb5fbc873b 336 -> 160 bytes; the scene pass 5.1 -> 3.2 ms. The cache-hit path (fin calls ps_libraries on the restored correcting source), the per-draw border_key comparison and the g_ntwin early exit come from the iPhone port (seedds#1073). Co-authored-by: givethesourceplox <272928635+givethesourceplox@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I ran the same border split design on an iPhone 17 Pro Max (A19 Pro, iOS 27.2) with Donkey Kong Bananza, so here is the phone-side evidence. The build was a local tree that carries a border split like the one in #1032, with shader chain 215a6d278114. It was not this PR's exact head. On a 70-second movement and attack route, six complete 300-frame reporting windows held a p99 of 33.34 ms with no dropped or discarded frames, at thermal state 0. The scaler logged 1676x943 to 2304x1296, the drawable measured 2346x1320, and the footprint ended at 5131 MiB with 3060 MiB of headroom. All 16 probed Metal 4 archive bases were found. This doesn't isolate what the split does for performance. The comparison run before it was warmer (thermal state 1) and the two views weren't pixel matched. I'm not claiming a pacing or fur shimmer change from it. |
…sampler with a twin: every pixel shader with border-colour corrections declares the function constant dm_border_off (DMSH_BORDER_FC, 216) and tests it before the mask bit in dm_bs, and the host specializes each function with it true when no sampler bound to the draw's stages has a border twin (border_need, pso_key.border), so the branch and the registers it kept live across every sample are gone from the pipeline instead of skipped per pixel. The mask branch made Apple's compiler keep the sample's operands for the correction that never ran, which spilled registers in shaders with many samples. A pipeline builder takes an explicit off flag from the draw's key (pso_build, pso_build_ex, pso_build_dev, pso_desc4, pbase_key, cps_new_b, the mesh and capture pipelines, the prewarm and pso_offline.py's archives), and the specialized function is found there (fn_off, fn4_of_b, fn_dev_b); descriptors hold only real Metal functions. A draw compares its stages' need with the bound pipeline's (prog_border), so a sampler with a twin bound after the pipeline was picked gets the correcting pipeline. DM_NVN_BORDER_KEEP=1 keeps the corrections in every pipeline and =ab alternates by DM_AB phases. Ocarina of Time standing on the forest deck, in-process A/B on an Apple M6: GPU p50 10.66-10.88 ms without the corrections, 11.01-11.26 ms with them, frame time 16.67 ms both. Compiled for the M1's GPU (applegpu_g13g, 300 of its pixel shaders): mean code 16,232 -> 14,531 bytes, per-thread scratch lower in 77 shaders, higher in 11, the worst 240-352 bytes -> 0. AdyaGMD's measurement of the side-library form on an M1, Splatoon Raiders: 31.6 -> 19.8 ms a frame; not reproduced here. t_border (113 checks), the 136 gfx suites, test_dmsh_border, test_pso_offline, test_docs pass; regress.py check on Bastion and NieR:Automata: 60 fps, every shader builds, no new red line. The side library of PR #1203 is not taken: it compiles every pixel shader with corrections twice. Not run: Tears of the Kingdom (the one known game that binds a twin; its build is not on hand), the iPhone, an M1. The function constant is FeReuss's design and code (PR #1929); the diagnosis of the spill and the M1 numbers are AdyaGMD's (PRs #1203, #1032); the need for a per-draw choice is givethesourceplox's finding (PR #1073) Front-ends: the shader chain version changes; shaders are rebuilt. Co-authored-by: FeReuss <102842515+FeReuss@users.noreply.github.com>
|
The problem this PR found is fixed on main by 511a0a2, in the form of PR #1929: a function constant that the host sets when no bound sampler has a border twin, so the correction's branch is compiled out of the pipeline. The diagnosis (the mask branch keeps the sample's registers live and spills) and the M1 measurement of Splatoon Raiders, 31.6 -> 19.8 ms, are credited to you in the commit message. The side library is not taken because on today's main it compiles every pixel shader that has corrections a second time and stores a second library for it, where the constant costs one specialization of the pipelines that need it. Compiled for the M1's GPU (applegpu_g13g), 300 of Ocarina of Time's pixel shaders have 10% less code with the constant set and 77 of them less per-thread scratch (the worst from 240-352 bytes to 0). Not measured here: Splatoon Raiders or any game on an M1. If you can rerun your scene on main, the frame time there would settle whether the constant gives all of the 31.6 -> 19.8 ms. Thank you. |
The same fix as #1032, rebuilt on top of #1036's shared alpha test and logic operation compile. #1032 can't be rebased without a force push, and the side-library code it touched has been rewritten since. I've left #1032 and #1073 open so you can choose: if this one lands, #1032 can be closed, and so can the border part of #1073.
What and why
border_emuwraps every sample through a sampler member indm_bs(…), a branch the host turns on only for a sampler whose border colour Metal can't express. That branch almost never runs, but the GPU allocates registers for it in every pixel. In the shaders below, that was enough to push them into spilling.ps_librariescompiles<key>_bfrom the source without the correction (border_plain: eachdm_bs(sample, …)is turned back into the sample). The twin samplers and thedm_borderblock stay declared, so the argument buffer is identical either way.dmsh_side.unit) is cut into<key>_bb, the shader's own entry points with the correction, and<key>_bx, the alpha test and logic operation entry points. So_bxkeeps correcting, as before. A shader without a compile unit gets<key>_bbfrom the correcting source as a whole. A shader without corrections is built exactly as onmain, and a stale_bbis removed.borderwhen_bbexists, andpublish_cachedrops it so a cache hit makes the libraries again from the restored correcting<key>_b.metal.border_keysets bit 1 ofpso_key.logicwhile a sampler member of the pixel shader has a twin.bb_fnthen loads_bbon first need, and the draw comparesborder_keywith the last pipeline's, since binding a sampler doesn't mark the pipeline dirty.g_ntwinlets it skip looking at the bindings when no registered sampler has a twin. The existingk->logicchecks test bit 0.The cache-hit path, the per-draw comparison and
g_ntwincome from @givethesourceplox's iPhone port (#1073), credited in the commit.Measured (M1, handheld)
c0ae76e0ca76b574: spill per thread6f9fd9cb5fbc873b: spill per threadThe pass dump shows that Splatoon pass binds no sampler that needs a border colour, so all its draws use the plain library.
These numbers come from builds of the same fix on the older per-variant side libraries (
_ba,_bl,_bb). This branch is the port onto_bx. The spill counts come from compiling withmetal-tt -arch applegpu_g13gand reading the per-thread scratch size.Tests
test_dmsh_border.pygetsborder_plaincases: every sample taken back, the argument buffer unchanged, a source without corrections left alone, and the result compiling.t_bordergetsborder_key's choice of library for a sampler with a twin, with Metal's own colour, with none bound, with a member that has no bit, for a constant sampler ID, and for a shader without_bb.I didn't run the regression suite or the tests locally.
🤖 Generated with Claude Code