Repository navigation
Conversation
…0 passes a frame stay open in Ocarina of Time fb_prepare took every draw with pool handles or dynamic tables as a depth reader, because only the GPU knows what the handle names, and the next draw writing depth ended the pass. Ocarina of Time's material shaders are pool shaders that write depth, so each draw ended the pass the one before it opened. PucaVaz found the rule and the cost in Link's house (issue seedds#1385, PR seedds#1396), and that asking the pool tables doesn't help: the game registers its depth textures in the pool those shaders read. A draw that writes depth itself is no longer taken as a reader: sampling the attachment it writes would be a feedback loop, undefined on the console too. In Kokiri Forest the reader wrote depth in 67,730 of 67,739 splits. Pool draws that leave depth alone still count, as do real slots and TEXU handles. This is an inference from the API, not a proof about any shader. DM_NVN_FB_DSELF=0 counts them as before; ab alternates with DM_AB. On the author's stack (M5 Pro, 1280x720, game upscaler, encoding thread off, A/B over 6 paired phases): 183 to 112 passes a frame, attachments 286 / 316 to 244 / 273 MB, GPU p50 20.3 to 16.5 ms, frame p50 20.7 to 16.8 ms. Not run on this commit, on main, or on another game. Check: check_t_feedback, with a new case: 176 checks, 0 failed; with the change taken out, 3 of them fail. test_docs, test_env_switches, test_gfx_tests_makefile, docs_symptoms.py --check; lint_c.py tidy on nvn_mtl.m and t_feedback.m: 0 file/check pairs above the baseline.
… longer counts as reading it: about 70 passes a frame stay open in Ocarina of Time
|
Played on a branch that has #1299's profile, since this one can't be run alone. Setup: Apple M3 Max, 36 GB, macOS 27.0.1. Ocarina of Time v131072 from a NUP, Mac build, windowed (2560x1440 window, 1536x864 scene), Kokiri Forest autosave. The branch is A/B:
Every pair moved the same way: GPU p50 5.2–6.0 ms lower. The p90 drops by about 8 ms, so the frame pacing is steadier too. #1396's by-cause line in an earlier run on the same branch had 59.8–74.4 depth splits a frame after pool or dynamic shaders, and 0.0–0.1 for every other cause. Picture: one screenshot with Rule off ( Rule on (default, HUD GPU 25.5 ms): Stacking on #1396, two notes:
|
|
Thanks, a second machine and a bigger scene is what this needed. You are right about the early return: the rule belongs inside |
|
A third machine, a 16 GB one where the GPU is the limit: Mac mini M4, macOS 27.2, docked (2048x1152 scene in a 2560x1440 window), v131072. In earlier
That's about 31 → 37 fps outdoors (in Kokiri Forest, by a chest) and 24 → 39 fps in the house. Attachment traffic drops by only 9–18 MB a frame here, against 58 on the M3 Max and 42 on your M5 Pro, so on this M4 most of the gain doesn't come from attachment traffic. Picture, as a pixel diff. In Link's house, Both runs logged only the REDs this build always logs (the title movie's AV1 packet; slot 7's texture type mismatch). No GPU errors, and both exited cleanly. Drafted with Claude; reviewed by me. |
|
I ran this PR's rule against the snapshot follow-up to #901 that I'd been building (pool draws read a copy of the depth attachment through their pool tables instead of ending the pass), in one process: Ocarina of Time, Link's house, docked, MacBook Air M4,
Paired against the neighbouring phases of today's rule (frame / GPU span p50): DSELF −5.61 / −6.06 ms, the snapshot −4.98 / −5.45 ms. Snapshot minus DSELF: +0.68 / +0.65 ms. A second, throttled run gave the same order (DSELF −5.86 / −5.87, snapshot −4.90 / −4.62). In this scene the snapshot buys nothing over this rule and costs copies and table builds, so I'm not opening it. Do the draws read the depth they write? In my runs the passes end between material draws that write depth into a 128×128 Depth32Float target (222 of 227 in a window of 400 passes), each after a reader that wrote depth itself, which matches your 67,730 of 67,739 and @deanpektas27's 128×128 count. A diagnostic build read every 32-bit word of the uniform and storage buffers bound to every pool draw with a depth attachment as a texture handle, in windows of about two frames. For the 128×128 target (220–225 such draws a window, all writing depth, about 3.2 GB scanned) no word named either of its two pool IDs, in 3 of 4 runs; in the other run 24 words a window matched its IDs that time (0x201c, 0x201d) and I didn't capture where. The remaining few splits are on a second 1920×1080 Depth32Float target, drawn by the shadow-receiving materials ( Picture. Screenshots at frames 4,800, 5,400 and 6,000 at a fixed render scale ( Drafted with Claude; reviewed by me. |
…r counts as reading it: about 70 passes a frame stay open in Ocarina of Time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
|
Thank you, your numbers independently confirmed the cause. Main took a different rule (a shader counts as a depth reader only once a depth texture was seen read through a pool, 6121fd7), as yours assumes a shader never reads the depth it writes. A counter now shows if a later area needs your condition too; if it does, this is the idea to revisit. Final checks on the rebased tree (full shader build, walks, regression) are still running, so please report anything you see. |
) replaces the pull request heads Upstream landed Ocarina of Time in main (chain adec7d81d970: "holds 60 fps in Kokiri Forest, 4 GB less") and closed the pull requests it folded in, with reworded and partly different code. Mixing a pull request head with its landed version hunk by hunk silently broke things (two GlobalToStorage tables, a duplicate AF2ip, two mtk_pso definitions), so the rule here is one version per feature: - Pull requests upstream closed (seedds#1299, seedds#1300, seedds#1312, seedds#1343, seedds#1345, seedds#1353, seedds#1354, seedds#1359, seedds#1360, seedds#1373, seedds#1428, seedds#1430, seedds#1434): their hunks are taken out of our side before the three-way merge, so upstream's landed version stands alone. Gone with them: bindless pool table patching (reg_bump), the ghost table's per-submission hold, the growing raw pool table and its write-log sizing, seedds#1428's pool_buf spares (upstream's pool_table_take is the same idea), the ixc-based indirect mesh draw (upstream uses mdx), and their tests (t_pool_table, t_mesh_indirect, t_gmem_raw, finalized_bindless_pool, test_bnsh_nvn2, test_ndve). - Open pull requests that upstream's landing supersedes: seedds#1394 (upstream's mtl_pool_addressed) and seedds#1393 pool_skip_retired (upstream's budgeted pool walk; the profile does not use it) are dropped with their tests and docs. seedds#1368's mesh pipeline prewarm is dropped too: upstream kept the background build in nvn_geometry.inc, and the prewarm's mtk_build / mtk_claim / g_warm_mps went with it. - Kept, as upstream does not have them: NXForge's own features (GPU watchdog, present filters and anti-aliasing, aniso floor, quick settings, pipeline archive compaction, upload breakdown, memory report lines) and the still open pull requests seedds#1304, seedds#1305, seedds#1308, seedds#1320, seedds#1331, seedds#1332, seedds#1355, seedds#1356, seedds#1369, seedds#1376, seedds#1377, seedds#1379, seedds#1382, seedds#1390, seedds#1391, seedds#1392, seedds#1414, seedds#1420, seedds#1436, seedds#1439, seedds#1446, seedds#1453. - Combined where both sides changed the same lines: GW_N 1 << 16 with seedds#1320's wide-write intervals (GW_PAGES = SBW_WPAGES, 128 slots), the CPU upload log with upstream's linear upload route, arch_open_end before the compaction limit, cbn_prog in geo_gs_one (seedds#1382). - Translator: upstream's sources and binaries, plus seedds#1414's InstEmitAmpereMem diff and the LDSM SubgroupBarrier in InstEmitAmpereTensor.cs. The binaries and dmsh_chain.lock are upstream's until the Mac rebuilds them (dotnet publish, tool_lock --update-lock, dmsh_chain --header); dmsh_chain.h is regenerated for now. - RED_ONCE keys: upstream took 153 and 154, so the present anti-aliasing lines move to 156 and 157 and seedds#1420's packaged-entry line to 158 (array 159). - Host: hle_net.c and t_net_eventfd.c are upstream's (its eventfd poll waits out the whole timeout, which our 5d7920c fixed another way). - Kept ours: dm/tools/romfs_image.py and its test, so the 26 GB RomFS image is not packed again. dm/tools tests on Linux: same failures as on 875449e (Linux-only quirks), plus test_nlg4 and test_dmsh_pool_arrays, which fail on upstream main too. Not yet built on the Mac. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
…n Kokiri Forest, about 4 GB less memory) beside this tree's own work Kept from this tree: the growing raw pool table with two-word hints (plus upstream's read-before-OR on its write log), seedds#1439's held count, seedds#1436's diagnostics, seedds#1447's texture release, seedds#1435, the idle evictions, the GPU watchdog, range-tracked GPU writes, the packaged-texture cache (g_pkg_alias), the synchronous task / mesh pipeline build with prewarm, this tree's F2IP. Taken from upstream: the pool tables (only what shaders read is made, within a budget; pool draws count as depth readers only when one was seen reading, replacing seedds#1430's rule; table reuse), sub_cut's busy rule, the indirect task / mesh draw through mdx, the empty mesh fragment, the larger GPU-write table (65,536 slots), linear textures by rows, the atomic alias on any 32-bit format, its eventfds (with this tree's select and fcntl on them), its prepo Send and audio update lock, its shader split and Ocarina profile and tables. Duplicates both sides added were removed (SDK 22 answers, F2IP, test stubs); RED_ONCE 153 split; t_pool_table (the replaced pool table code) removed; tests adapted to the taken rules. Translator rebuilt; chain 695a9393d125. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


What and why
In Ocarina of Time (Switch 2,
0400486024DEC000, v131072; profile in #1299) about 70 render passes a frame end infb_preparefor no reader, as @PucaVaz found and measured in #1385 and #1396: a pool or dynamic-handle fragment shader could name the depth attachment, only the GPU knows, sofb_preparetakes it as a depth reader and the next draw that writes depth ends the pass. The game's material shaders are pool shaders that write depth, so each draw ends the pass the one before it opened. Asking the pool tables does not help (#1396): the game registers its depth texture in the pool those shaders read.Change: a pool or dynamic-handle draw that writes depth itself is no longer taken as a reader of it (
S.ds.dt && S.ds.dw): sampling the attachment you are writing is a feedback loop, undefined on the console too. Pool draws that leave depth alone still count (water reading the scene's depth), as do real slots and TEXU handles.DM_NVN_FB_DSELF=0counts them as before;abalternates withDM_AB. It is on by default; say so if you would rather have it opt-in.Evidence, and how far to trust it:
Stacking on #1396: that draft turns the line into
g_fb_dread = fb_depth_reader(fs), so the two conflict innvn_mtl.m(git merge-tree: that line, plus the generated docs). With #1396 merged first, the rule goes insidefb_depth_reader, on its dynamic and pool branches:int self = S.ds.dt && S.ds.dw && fb_dself();, thenif (fs->rf.dyn && !self)andif (fs->rf.pool && !self). It must not be an earlyreturn 0before them: that would also skip the finalfs->rf.bindless && fb_texu_reads(...)check, which keeps a TEXU handle that names the depth a reader (found by @deanpektas27 when stacking the two). With both in, #1396'st_fb_dpoolneeds a pool draw that leaves depth alone for its cases, as every pool draw in it writes depth now. I will rebase this onto whichever lands first.Reference entry: Pool shaders that write depth taken as depth readers. The symptom index is regenerated.
Checked
make -C dm/gfx/tests GAME=010015100B514000 check_t_feedback): 176 checks, 0 failed. New casedepth_self_case: pool draws that write depth stay in one pass; a pool draw that leaves depth alone counts as a reader and the next draw that writes depth ends the pass; with the rule off a pool draw that writes depth counts as before. With the change taken out offb_prepare, 3 of its checks fail.test_docs.py,test_env_switches.pyandtest_gfx_tests_makefile.pypass;docs_symptoms.py --checkcurrent;lint_c.py tidyonnvn_mtl.mandt_feedback.m: 0 file/check pairs above the baseline.make -C dm/gfx/tests run, the host tests,regress.py, any game on main. The rule applies to every game with pool or dynamic-handle fragment shaders that write depth; only Ocarina of Time is known to have them.Docs
docs/reference/rendering.mdandpython3 dm/tools/docs_symptoms.pywas run