Skip to content

nvn/feedback: a draw that samples no attachment skips the slot scan - #1469

Open
Xipzer wants to merge 1 commit into
seedds:mainfrom
Xipzer:fb-early-out
Open

Xipzer wants to merge 1 commit into
seedds:mainfrom
Xipzer:fb-early-out

Conversation

@Xipzer

@Xipzer Xipzer commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

With same-pass feedback snapshots on, fb_prepare and fb_tile_plan looked up every fragment texture binding of every draw: a lock and a texture each, plus a retain and release in the plan.

Now, before the tile plan, one pass over the bindings' serials, under a single lock, compares them with the pass's attachments. A draw that samples none of them (BotW: about 14 draws in 15) returns there with its write mask noted. Separately, the plan looks a texture up only for a binding that names an attachment the pass wrote.

Breath of the Wild, Mac bench (130%, snapshots on): about 12,020 down to 11,930 instructions a draw; no-inline profiles show fb_prepare + fb_tile_plan falling from 187 to 131 samples. The picture is unchanged: t_feedback passes (193 checks), and per frame the snapshot counts (4 taken, 234 tile reads) match.

@github-actions github-actions Bot added the nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) label Nov 6, 2026
@Xipzer

Xipzer commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Note: I couldn't run scripts/regress.py check for this one. This Mac has Xcode 27.1 (Metal 32023.921), and the chain lock on main names 27.2's 32023.922, so the local games' shaders don't build against main's chain here. The measurements come from my integration branch, which carries the same change on a lock for 27.1. If you can run the check on your side, I'd appreciate it.

gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
@Xipzer

Xipzer commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main (04811a6); the early out now sits after fb_depth_rule (PR #1917): it looks at the colour attachments only, keeps g_fb_dwrote, and is skipped for a draw the depth rule gave a copy. The instruction counts in the message are from before the depth rule and were not measured again.

Snapshots: a draw that samples no attachment of its pass leaves fb_prepare after one look; the tile plan looks textures up only for attachments

fb_prepare and fb_tile_plan looked up every fragment texture binding of every draw (a lock and a texture each,
with a retain and release in the plan). A first pass over the bindings' serials under one lock returns for draws
that sample none of the pass's attachments, and the plan takes a texture only for a binding that names one.
Mac, BotW, snapshots on: about 12,020 -> 11,930 instructions a draw (11,030 with DM_NVN_FB_SNAP=0).

On the depth rule (PR seedds#1917): the look names the colour attachments only, as fb_depth_rule settles the depth
attachment before it; it keeps g_fb_dwrote, and a draw the depth rule gave a copy takes the long way. The
instruction counts are from before the depth rule and were not measured again.
@Xipzer Xipzer changed the title Snapshots: a draw that samples no attachment of its pass leaves fb_prepare after one look nvn/feedback: a draw that samples no attachment skips the slot scan Nov 13, 2026
@Xipzer

Xipzer commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main (2e6edda) without conflicts, retitled to the area/component form.

Checked on this commit (M4 Max): host build, make -C dm/gfx/tests run (every target passes), the settings / docs / switch-table tool tests. The host tests pass except audio-device checks (t_audio's device stall here, t_audio_device on clean main in the same session).

Xipzer added a commit to Xipzer/nxnative that referenced this pull request Nov 13, 2026
A draw that samples an attachment it writes reads the tile when it opens its pass too, not only in an open one: Breath of the Wild opens about 3.75 passes a frame with such a draw, each a full-size snapshot before (30,000 snapshots to 4 over 11,000 frames on an M4 Max)

fb_tile_plan needed an open pass, since the tile-reading pipeline is named by the pass's formats, so a draw that met no pass took a snapshot: a full-size copy, where the pass it opens loads the same content into the tile. Breath of the Wild's 753d0169ae3ce4f0 and 84862343ae82c6fa read G-buffer targets they write and open their pass.

The formats are now learned. The first draw of a program on a set of targets that opens its pass and samples an attachment it writes takes its snapshot, and fb_early_after notes the pass rp_begin_ex opened for it (64 entries, keyed by the two shaders and the targets). Later ones plan the tile read with those formats while no pass is open and are checked once the pass opens: a pass that differs (formats, size, slice-merged) is ended, the draw takes its snapshot through fb_prepare again and the pass reopens; three such passes and the entry plans no more. A clear still waiting becomes the pass's load action, so the tile holds the cleared content, as the snapshot does since b54968e. Where fb_depth_rule ends the pass before the plan, the draw counts as opening its pass. DM_NVN_FB_TILE_EARLY=0 turns it off.

Measured on a fork that carries the change over an older main (413e880 plus the fork's commits), Breath of the Wild. M4 Max, 190% render scale, uncapped: snapshots 30,000 to 4 over 11,000 frames, GPU p50 5.99 to 5.56 ms and 6.9 to 5.9 ms in two alternated pairs, pictures the same. iPad Pro 13" M4, 120% render scale, target 120 fps: snapshots 150 a second to 0; GPU p50 10.88 to 10.77 ms and 11.62 to 10.90 ms, 75.9 to 75.6 fps and 70.7 to 74.7 fps in two alternated pairs (the iPad's runs drift with its temperature). That fork's fb_prepare returned early for a draw that samples no attachment of its pass (PR seedds#1469) and had no depth rule; here the plan itself notes only draws that sample an attachment they write.

Checked: check_t_feedback, 293 checks under Metal validation, 0 failed, with new cases (nothing learned by a draw that writes no attachment it samples, the first pass-opening draw's snapshot and the noted pass, the next one's plan with no pass open and its pipeline, the GPU reading green and red that the opened pass loaded, a refused pass, the snapshot after it, no plan after three). The host builds for 01007EF00011E000, the iOS compile check has 0 of 262 failed, test_env_switches, test_docs, test_lint_c and test_architecture pass. Not run on this main: any game (regress.py check).
@Xipzer

Xipzer commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Now that this Mac has the chain lock's compiler (Xcode 27.2), scripts/regress.py check on Breath of the Wild 1.9.0, main (2e6edda) against this branch, replacing my earlier note that I could not run it. Two runs: the first was flagged on its 80 s screenshot only, the second (below) is CHANGED on screenshots within tolerance.

regress.py check (full): CHANGED

Baseline 2e6edda4d334feaf48326407c1718cb96c43e2de 2e6edda, candidate 0c6d5d6d2d85e1980d3f30b279899f3702d3c9d9 0c6d5d6; Apple M4 Max, macOS 27.2.1, Xcode 27.2; 297.4 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
BOTW 01007EF00011E000 CHANGED 19592/19592 → 19592/19592 0 0 0 0 / 0 ok 59.8 → 59.7 17.83 → 17.26 3243 → 3244 0.05 → 0.05 0

BOTW 01007EF00011E000: CHANGED

  • CHANGED: screenshot at 60 s moved (RGB 6.98)
  • CHANGED: screenshot at 80 s moved (RGB 6.68)
    • screenshots (s: dHash bits / RGB diff / baseline noise): 60: 11/6.98/0, 80: 10/6.68/0
    • baseline: exit None, first frame 2.1 s, max frame 6270, reached True, prepare 0.05 s, prebuild 168.7 s (15767 built, 0 failed), fps 59.8, p50/p99 16.67/17.83 ms, peak 3243 MB
    • candidate: exit None, first frame 1.9 s, max frame 6283, reached True, prepare 0.05 s, prebuild 167.6 s (15767 built, 0 failed), fps 59.7, p50/p99 16.67/17.26 ms, peak 3244 MB
    • time: chain 18.8 s, smoke 278.5 s

First run's screenshots: screenshots (s: dHash bits / RGB diff / baseline noise): 60: 13/4.62/0, 80: 13/9.35/0

Screenshot noise of this route with my save: main (2e6edda) checked against itself three times gave PASS (80 s: RGB 2.88), PASS (3.17) and REGRESSION (60 s: RGB 11.6, 80 s: 34.28). Cloud shadows cross the field where my save stands, so a screenshot flag on this route does not separate a change from the weather. Every other column is the same in all runs.

The route's save is my own outdoors save, not the one smoke.tsv was written with. The early out is always on, so the route runs it.

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

Labels

nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant