Repository navigation
Pool tables for texture handles traced to no slot reuse their buffers and pass over IDs of another type - #1428
enjoyerofslop wants to merge 3 commits into
Conversation
… and pass over IDs of another type: Ocarina of Time's residency commits 12 times fewer pool_gpu wrote every rebuilt table into a new buffer, 8 bytes per registered ID (264 KB at Ocarina of Time's 33,000), so each rebuild was an allocation, a residency-set addition and, behind the fence, a removal: the table's line led the residency report with thousands of additions every 300 frames. A replaced table now becomes one of 64 spares and a later rebuild writes it again once the submission that could still read it has completed (pool_buf / pool_buf_retire, against g_done); spares stay in the residency set, and argument blocks holding an old address are not reused past a rebuild (g_psnap_gen). A walk also looked every registered ID up before checking its type, so a 2D-array table made the views of retired 2D registrations again, which go at every submission end, only to turn them away. A registration now keeps the Metal type tex_by_id gave it (treg_t.tt, cleared when the ID is registered again) and a walk passes over an ID of another type. Ocarina of Time (with PR seedds#1299), Kokiri Forest, M5 Max, two runs per host: residency commits per 300 frames 664 and 678 -> 56, additions 3,136 and 2,500 -> 276; 1,487 table buffers made and 81,333 reused; 74 million lookups passed over. t_pool_table: a walk over 20,000 2D IDs with dropped views in a 2D-array table takes 0.08 ms instead of 3.2 ms. Frame times did not change beyond the runs' noise. The reference entry also records what is still open: the table holds a Metal texture for every registered ID of its type (1.56 GB that nothing read in Kokiri Forest), and marking reads from shaders cost 22 % of GPU time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…traced to no slot reuse their buffers and pass over IDs of another type Adapted to this tree's pool_gpu: its copy-and-patch path takes its copy from pool_buf and retires the old table through pool_buf_retire, as the PR asks for seedds#1299's path. The type cache (treg_t.tt) goes into pool_entry after the descriptor-class check (ttype_apart, PR 1319), which already turns a 3D ID away from a 2D table but puts 2D and 2D-array together: t_pool_table's walk over 20,000 2D IDs in a 2D-array table, 4.42 ms looking them up, 0.19 ms passing over them (M4). Its two checks of the 3D ID say what this tree does (turned away by its descriptor, no lookup). The pool log line keeps this tree's counters and adds table buffers made / reused and IDs passed over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revised brief: keep only PR seedds#1434 (9eeacbb), seedds#1332 (7f5f2fc), seedds#1417 (1c1d453), and seedds#1428 (f537147). Remove all other prior ports and their follow-ups while preserving published history. Other glass and Mac agents own those items. Update the scoped report and references; exclude disabled SDK hooks from hit diagnostics. Python tools match the baseline failure set; 12 docs/environment checks pass. Portable SDK gate and diagnostic checks pass GCC ASan/UBSan. Mac and Metal checks remain open.
…itch 2) playable at 60 fps through Dodongo's Cavern Resolved against this tree's own Ocarina work: - Kept this tree's raw pool table (grows past 4,096 entries, two-word hints, per-entry write logs); the PR's merged-run table (GMEM_RUN) and one-word hints are left out, with the ABI and translator to match. - Kept the pool table spares (seedds#1428), type cache, range-tracked GPU writes (seedds#1390), 1430's depth-read rule, the synchronous task / mesh pipeline build, and the item walk of NDVE packs. - Taken from the PR: ALIAS_IDLE 512 and targeted storage notes on takeover, packaged-texture recycling (pkg_tex_put), cached zero-copy upload sources (texture-only pools keep a per-batch buffer), retired CPU-written IDs without a view get no texture, mesh pipelines without a vertex function, image binding upgrades with pixel-format views, F2FP/I2FP and other translator fixes not already here (its second F2IP dropped), loop_guard and tg_fence, the GPU watchdog, the eventfd poll timeout, the profile (16 GiB alias region, mem_map_limit, tex_flush_upload, tex_dirty_upload) and its shader tables. - Tests adapted: t_alias_takeover (textures in a pool), t_cpu_tex (retired descriptors), t_res_guard (512-submission idle window), t_upgrade_alias (ghost entry), t_pool_chunks (contiguous pools in a growing table), t_present_alloc (layer sized to its pictures after upstream's crop change), t_hid (poolstat / texstat stubs). - Translator rebuilt from the merged source; chain fe655346ea1c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n and its resources before it uses them The test includes nvn_mtl.m, and with this branch's additions there the analyzer's path through the test's main() reached two calls it had not before: newComputePipelineStateWithFunction: with the function newFunctionWithName: may return nil, and copyFromTexture: with a texture newTextureWithDescriptor: may return nil (clang-analyzer-nullability.NullablePassedToNonnull, 1 in the file against a baseline of 0). The function is now checked before the pipeline is made, and the textures and buffers after they are made; the test fails with a message either way, as it already did for the library and the pipelines. lint_c.py tidy on the file: 0 findings (baseline 240, none above or below); the test passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss over IDs of another type (Ocarina of Time's residency commits 12 times fewer) Resolved against NXForge's pool table (seedds#1360's patching, seedds#1393's pool_skip_retired): - pool_entry passes over an ID whose registration resolved to another texture type (treg_t.tt) after the gone check, so both the full walk and the patch path skip it - the full build and the patch path take their buffers from the spares (pool_buf) and retire the replaced one (pool_buf_retire); pool_snap keeps its sub field - the pool table log line keeps NXForge's why/patched/kept counts and adds made/reused/passed over - t_int_float_view.m keeps main's checks; the Makefile has t_gmem_raw and t_pool_table - t_pool_table marks the 2D table out of date before its second rebuild, since a patch on registrations alone would not walk the 3D ID Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
…e the place of this branch's 6121fd7 brought both halves of this pull request into main ("the idea is from PR 1428"): pool tables reused once their fence has passed (pool_table_take / pool_table_give, sizes in 64 KB steps, 16 spares) and an ID of another texture type passed over by its registration's type (treg_t.tt1), in a walk main has reworked (a budget, textures shaders read made first). This branch's own versions (pool_buf / pool_buf_retire, POOL_SPARES 64, treg_t.tt) would have doubled them, so nvn_mtl.m is main's, as is docs/reference/nvn-objects.md (main documents the reuse in memory.md, "Pool tables and readback buffers made anew each frame"; the symptom index is regenerated). What stays: t_pool_table, now on main's names (g_pool_made, g_pool_reused, tt1), which main has no test for: a spare is not written again before its submission completes and is after, a 3D ID in a 2D table is passed over without its view being made again, a new registration forgets the type, and a walk over 20,000 2D IDs with dropped views passes over them in under a quarter of the time of looking them up; and the clang-tidy fix to t_int_float_view. t_pool_table 20 checks, t_cpu_tex 59, t_pool_chunks 102, t_int_float_view ok, test_docs OK, lint_c.py tidy on both tests 0 findings (baseline 240, none above or below). t_feedback fails two of its 161 checks ("the fixture's shaders compile") with main's code unchanged here: its new cases had not run when 6121fd7 landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you. Both ideas, reusing replaced pool tables and passing over IDs of another type, are on main in 6121fd7 and credited in docs/reference/memory.md. Tables made fell to 11 in 11,700 frames here. Long frames didn't change from this alone, which matches your measurement. Final checks on the rebased tree (full shader build, walks, regression) are still running, so please report anything you see. |
|
For the record, measured on an M3 Max (36 GB, macOS 27.0.1, Xcode 27.2) with Ocarina of Time v131072: this change adapted on top of #1430 and the 2026-11-05 main doubled the long frames in a 60 s window standing at the Kokiri balcony autosave ( |
) 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
What and why
Symptom: in a game whose shaders read the texture pool (the
pool for handles traced to no slotlines), the pool table's source line leads the residency report. Every 300 frames it shows thousands of residency-set additions and removals and hundreds of commits. Found on The Legend of Zelda: Ocarina of Time (Switch 2,0400486024DEC000), where 4,823 of 42,854 shaders read the pool.Cause: two things in
pool_gpu(dm/gfx/nvn/nvn_mtl.m).Change:
pool_buf_retire). A later rebuild writes it again once the submission that could still read it has completed (pool_buf, againstg_done). Spares stay in the residency set. Argument blocks holding an old address are not reused past a rebuild (g_psnap_gen, unchanged).tex_by_idgave it (treg_t.tt, cleared when the ID is registered again), and a walk passes over an ID of another type without looking it up. The type is fixed by the descriptor (its target, the view's target and the layer count), so it outlives the cached view.table buffers made,reusedandIDs of another type passed over.Reference entry: Pool tables rebuilt into new buffers and walked over IDs of another type. The symptom index is regenerated. The entry also records what is still open: the table holds a Metal texture for every registered ID of its type, whether or not a shader reads it. In Ocarina of Time's Kokiri Forest that was about 4,200 textures (1.56 GB) that nothing read. Marking reads from the shaders to make them on demand cost about 22 % of GPU time, even with the marking switched off, because a fragment shader that can write memory runs its depth test after itself.
On #1299: that PR adds a copy-and-patch path to
pool_gpu. With this change, its copy should come frompool_buf(p->buf.length)and the old table should go throughpool_buf_retire(p->buf), and itspool_entryshould get the same type check beforetex_fast_rid. The Ocarina of Time numbers below are frommain+ #1299 + this change, merged that way.Checked
make -C dm/gfx/tests GAME=01007EF00011E000 t_pool_table: 20 checks OK. It covers:t_cpu_tex(62) andt_pool_chunks(81) also pass on the Ocarina of Time tree (main+ [WIP] The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000, v0, SDK 22.2.8) runs on the Mac through the title movie into Kokiri Forest #1299 + this change).make -k -C dm/gfx/tests GAME=01007EF00011E000 run: every test passes butt_nvn. It aborts inup_owner_check("upload batches belong to the submit thread only: up_begin called on thread ''", frompresent_enqueue→tex_mtl→pkg_upload), and it aborts the same way onmain(865cc35) without this change.python3 -m unittest dm/tools/tests/test_docs.py: OK.0400486024DEC000, update v131072, with [WIP] The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000, v0, SDK 22.2.8) runs on the Mac through the title movie into Kokiri Forest #1299.Setup: Kokiri Forest from the same save, along the same scripted route (walking, camera turns, dialog). About 250 s per run, two runs per host, alternated. The game does not run under
DM_DETCLOCK, so the runs are on the wall clock and their timings drift apart.Results (per 300 frames of gameplay, medians):
Spare count: with 8 spares instead of 64, 41,483 tables were made: the oldest spare was pushed out before it came free.
Frame times: p50 is vsync-bound at 16.4 ms in all runs. p95 and long frames (48 to 52 per 1,000 frames) moved within the runs' noise. Time per full pool build was 2.07 and 3.64 ms in the two runs before and 2.95 ms after, so the before runs alone differ by more than the change.
python3 scripts/regress.py check --baseline 865cc35f --candidate HEAD --games 01007EF00011E000,0100000000010000(the full run), on the two NVN games this Mac has--platform macbuilds of.regress.py impactlists Breath of the Wild among the games the change reaches, through the registration and lookup bookkeeping every NVN game has. Neither game has a shader that reads the texture pool, so the table code itself does not run in them.saves/outdoorsandsaves/look_aroundare not on this Mac.regress.py'sshot_hashes/shot_distanceacross five runs of 865cc35:validated.tsvwas not recorded, as the routes did not run from the profiles' saves.regress.py check (full): REGRESSION
Baseline
865cc35f865cc35, candidateHEADf537147; Apple M5 Max, macOS 27.0.1, Xcode 27.2; 918.6 s01007EF00011E0000100000000010000The Legend of Zelda Breath of the Wild
01007EF00011E000: REGRESSIONSuper Mario Odyssey
0100000000010000: CHANGEDCHANGED: screenshot at 95 s moved (RGB 10.44)
Devices and OS: M5 Max (Mac17,6), macOS 27.0.1.
Docs
docs/reference/nvn-objects.mdentry, andpython3 dm/tools/docs_symptoms.pywas run (the symptom index)🤖 Generated with Claude Code