Repository navigation
Donkey Kong Bananza on iPhone: border corrections out of the main pixel libraries, NDSR replacements, pacing and fence fixes - #1073
Conversation
…el libraries, NDSR replacements, pacing and fence fixes Border colour corrections (builds on seedds#1032): a pixel shader's main library is built without border_emu's dm_bs branch, which spilled registers in every pixel where it never ran; the correcting source goes into a side library <key>_bb, taken only while one of the shader's samplers has a twin (pso_key.logic bit 1). On top of seedds#1032: - <key>_b.metal keeps the correcting source, so a cache hit makes _bb, and the alpha test and logic libraries, with the correction (with the stripped source there it made none); - binding a sampler does not mark the pipeline dirty, so the draw compares the border bit and fetches the pipeline again when it changes; g_ntwin skips that look while no registered sampler has a twin; - blending and the dual-source entry point test logic bit 0 only: the border bit turned both off (t_missing_src1 fails without this). Mac M1 Pro, Banana Mine, 30 fps cap, scale 0.5: GPU 15.4-15.6 -> 14.0-14.1 ms a frame, the G-buffer pass 2.4 -> 1.6 ms, the output frame unchanged. NDSR replacements (DM_NVN_NDSR, off by default, reviewed 2x kernels from the profile's ndsr_spatial.tsv only): metalfx runs MetalFX spatial HDR on the Metal 3 queue ordered by shared events; bilinear is one bilinear draw in the replay's own command stream. On an iPhone 15 Pro Max NDSR's dispatch takes 15.5-16.7 ms of a 46-48 ms GPU frame; on the M1 Pro 2.78 ms -> 0.08 ms (bilinear), whole frame 14.12 -> 12.22 ms (bilinear) / 12.67 ms (metalfx). No phone measurement yet. Fixes: - Present snapshots: the acquire waits for the display link's present-interval ticks; the ring slot wait alone let DK at the 30 fps cap run 43-65 fps of game time and drop ~110 of 300 frames. - Queued future fences: guest waits on a fence the submit worker published but has not committed yet take the shared-event path instead of waiting for CPU feedback (t_perf_order). - Segmented submissions count draws from mtl_sub_begin, so a submission drawing into the target it inherited no longer leaves its first pass's draws out (t_reorder_chain). - A mip-chain pass whose sampler pins one level samples that level alone (fb_pinned_level): DK's bloom upsample read a sharper level, the stage intro's neon letters came out fat and filled. - HMMA with an F16 accumulator sums in FP32 (simdgroup_float8x8), as the hardware does. - iOS: the prepared project and unsigned IPA ask for Extended Virtual Addressing when the guest reservation is over 6 GiB (gen_entitlements.py); DM_CPU picks the -mcpu target (apple-a17 for iPhone 15 Pro); DmGamepad builds with the iOS 27 SDK. Co-authored-by: Adya <114347520+AdyaGMD@users.noreply.github.com>
…he level its sampler pins, and a submission's segment count starts with it (from #1073) - Present snapshots: with the display link ticking, the acquire waits for the present interval's ticks like any present; only a stalled link falls back to the texture's slot time. The ring slot wait alone did not pace: the link drops all but the newest two frames and a dropped frame frees its slot, so Donkey Kong Bananza at the 30 fps cap ran 43-65 fps of game time and dropped about 110 of every 300 frames (measured by the PR's author). t_link_ticks checks the acquire with snapshots on. - Same-pass feedback in mip chains: a sampler whose LOD clamp pins one level (fb_pinned_level) gets a view of that level alone, on either side of the attachment. The view of the levels below clamped an upsample's read of level n + 1 to n - 1, a sharper level (DK's bloom: fat, filled neon letters), and an upsample into the view's base level read the attachment itself. t_feedback mip_chain_case. - Segmented submissions: the draw count starts in mtl_sub_begin, not at the first sub_cut, which only a target change reaches; draws into an inherited target were left out. t_reorder_chain inherited_target. Regress (M4 Pro): BotW PASS; TotK screenshots differ only because the baseline run lost window focus three times and fell behind (38.5 fps, frame 4241 vs 5294; 0 changed MSL, 0 new RED). Co-authored-by: givethesourceplox <272928635+givethesourceplox@users.noreply.github.com>
|
Thanks for this. Three of the fixes landed in 56e4577, with you credited as co-author:
Regression run against main on an M4 Pro: BotW passes. TotK was flagged only because the run on main lost window focus and fell behind; both builds' pictures look right. The rest is still open here:
Also: the tests and measurements were run with Xcode 26.5, but |
…he level its sampler pins, and a submission's segment count starts with it (from seedds#1073) - Present snapshots: with the display link ticking, the acquire waits for the present interval's ticks like any present; only a stalled link falls back to the texture's slot time. The ring slot wait alone did not pace: the link drops all but the newest two frames and a dropped frame frees its slot, so Donkey Kong Bananza at the 30 fps cap ran 43-65 fps of game time and dropped about 110 of every 300 frames (measured by the PR's author). t_link_ticks checks the acquire with snapshots on. - Same-pass feedback in mip chains: a sampler whose LOD clamp pins one level (fb_pinned_level) gets a view of that level alone, on either side of the attachment. The view of the levels below clamped an upsample's read of level n + 1 to n - 1, a sharper level (DK's bloom: fat, filled neon letters), and an upsample into the view's base level read the attachment itself. t_feedback mip_chain_case. - Segmented submissions: the draw count starts in mtl_sub_begin, not at the first sub_cut, which only a target change reaches; draws into an inherited target were left out. t_reorder_chain inherited_target. Regress (M4 Pro): BotW PASS; TotK screenshots differ only because the baseline run lost window focus three times and fell behind (38.5 fps, frame 4241 vs 5294; 0 changed MSL, 0 new RED). Co-authored-by: givethesourceplox <272928635+givethesourceplox@users.noreply.github.com>
…ing them: lint-c's ratchet passes again (seven nullability findings from seedds#1086, seedds#1084 and seedds#1073, whose own runs were cancelled)
…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 border-correction part of this PR is covered on main by 511a0a2: a function constant compiles the corrections out of every pipeline that binds no sampler with a twin, and a draw checks its stages' need against the bound pipeline's, which is the per-draw choice this PR showed was needed (credited in the commit message). The side libraries are not taken. The other parts (NDSR replacements, pacing, fences) are not touched by that commit and stay open here. Not run: Donkey Kong Bananza on an iPhone; a retest of the border part on main would be welcome. |
What and why
Donkey Kong Bananza (
040074A01BF12000, v458752) reaches gameplay on an iPhone 15 Pro Max, but the GPU is the limit there: 46–48 ms a gameplay frame at about half render scale, and the phone soon throttles. This PR has the general fixes found while profiling it. Game-specific patches (widescreen, 40 fps, render-scale floor) are not part of it.Border colour corrections out of the main pixel libraries (builds on #1032, co-authored by @AdyaGMD).
border_emu'sdm_bsbranch took registers in every pixel where it never ran. A pixel shader's main library is now built without it (border_plain), and the correcting source goes into a side library<key>_bb, taken only while one of the shader's samplers has a twin (pso_key.logicbit 1). Three fixes on top of #1032:<key>_b.metalkeeps the correcting source, so a cache hit makes_bb, and the alpha test and logic libraries, with the correction. With the stripped source there, a cache hit made none of them.g_ntwinskips that check while no registered sampler has a twin.logicbit 0 only. With Pixel shaders without the border-colour correction in their main library: it spilled registers in every pixel where it never ran #1032 onmainas it is, the border bit turned both off.t_missing_src1has a new check that fails without this fix.If this lands, #1032 can be closed.
NDSR replacements, opt-in (
DM_NVN_NDSR=metalfx|bilinear). Only the reviewed 2x kernels listed in the profile'sndsr_spatial.tsvare replaced; anything else runs the game's shader.metalfxruns MetalFX spatial HDR on the Metal 3 queue, ordered with the Metal 4 queue by shared events.bilinearis one bilinear draw in the replay's own command stream. On the phone, NDSR's single dispatch takes 15.5–16.7 ms of the frame. Reference: Reviewed spatial upscalers on MetalFX.Fixes
perf-order-cpu,t_perf_order).mtl_sub_begin. Before, a submission drawing into the target it inherited left its first pass's draws out of the count (t_reorder_chain --submission-overlap-only).fb_pinned_level). DK's bloom upsample read a sharper level, so the stage intro's neon letters came out fat and filled.simdgroup_float8x8);test_dmsh_hmma.pyadds an HDR-sized input case.gen_entitlements.py). Without it, Play aborted before graphics with DK's 9.75 GiB block.DM_CPUpicks the-mcputarget (apple-a17for iPhone 15 Pro).DmGamepad.mbuilds with the iOS 27 SDK.The shader chain changes (
dmsh_chain.hregenerated).Checked
make -C dm/gfx/tests GAME=040074A01BF12000check_t_border,check_t_ndsr,perf-order-cpu,check_t_perf_order,check_t_reorder_chain,check_t_logic,check_t_alpha,check_t_missing_src1,check_t_integer_blend,check_t_link_ticks, andcheck_t_present_snap(01007EF00011E000);test_dmsh_border.py,test_dmsh_hmma.py,test_ipa_unsigned.py,test_docs.py;lint_python.py(no new finding in the files changed here). The fullmake -C dm/gfx/tests GAME=040074A01BF12000 runpasses.main'snvn_mtl.musesMTL4FXTemporalScaler's*ContentOffset*properties, which the 26.5 SDK does not have. I commented those 4 lines out locally to build and test; they are not changed in this PR.DM_RENDER_SCALE=0.5, frames 85–118 s, one run at a time:DM_NVN_NDSR=bilinearDM_NVN_NDSR=metalfxAt 100 s the output frames with and without the border change differ by a mean 1.12/255; two runs of the same build differ by 1.65/255. Bilinear differs from NDSR by 1.07/255 and is visibly softer; MetalFX differs by 1.28/255 and looks close to NDSR.
scripts/regress.py check --games.regress.py impactsays the change reaches every NVN game (nvn_mtl.m,hle_nvn.c), and DK's full shader build (22,852 shaders, 12,897 with a_bbside library) was done on the fork's chain with the samedmsh_build.pychanges, not on this branch's chain92c201edd640.Docs
docs/reference/{rendering,frame-rate,shaders,memory}.md,docs/development.md,docs/env_switches.tsvand DK's README;docs_symptoms.pywas rundmsh_chain.py --headerwas run anddmsh_chain.his part of the change