Repository navigation
An encoder's end barrier flushes to the device only; the start barriers carry the resource-alias flush: Ocarina of Time's main submission's command allocators no longer grow to 36-80 MB each - #1556
Closed
capibawa wants to merge 1 commit into
Conversation
…rs carry the resource-alias flush: Ocarina of Time's main submission's command allocators no longer grow to 36-80 MB each - Symptom: CPU-visible Metal memory (footprint's IOAccelerator (graphics)) of several hundred MB in thousands of 32-48 KB regions, most of it the 8 command allocators of the main submission. On Ocarina of Time (0400486024DEC000) before main's Ocarina fixes, scenes of about 150 passes a frame were slow while the GPU was not the limit. - Cause: while overlapping buffers or aliasing textures exist (large-pool chunks, image family heaps), resource_vis() adds MTL4VisibilityOptionResourceAlias, and m4_end put it on every encoder's end barrier (barrierAfterStages:beforeQueueStages:) as well as on its start barrier. On the end barrier it costs the command allocator about 100 KB per encoder; on the start barrier nothing measurable. An allocator keeps the size of the largest command buffer it held after a reset, so submissions of 600-1,400 encoders left all 8 at that size. - Fix: m4_end uses MTL4VisibilityOptionDevice. Every encoder the host opens on the main command buffer starts with a barrier carrying resource_vis() after all earlier work (draw passes, enc_r_begin, enc_c_begin, blit_open, the scaler's before MetalFX), so an encoder's writes are flushed for aliases before a later encoder reads them. A copy encoder that wrote through an aliased buffer address (g_blit_alias) keeps the flush on its end barrier. DM_NVN_END_ALIAS=1 brings the end flush back (ab alternates it with DM_AB). - DM_NVN_ALLOCPROF=2 gives each main submission a fresh allocator and reports command memory by operation, by encoder, by submission size, and the indirect-count multi-draws encoded. - Measured on an M3 Pro (18 GB): a test program's 600 render passes of 4 draws took 27.7 MB of command memory with the flush on start barriers only, 88.5 MB with it on end barriers too; device-only barriers on both sides read stale data through a second buffer over the same memory. Ocarina of Time in Kokiri Forest before main's Ocarina fixes: CPU-visible Metal memory about 800 -> 548 MB, frames 18-30 ms at p50 without the end flush against 31-45 ms with it (DM_NVN_END_ALIAS=ab). On main at 2eb7182: 392 -> 298 MB, the 8 allocators 181 -> 116 MB, no frame-time difference (A/B phase pairs within 2 ms). - Checked: check_t_copy_alias (126 checks, both encoding modes) with a new case, a shader writing through one no-copy buffer and the next encoder's shader reading through another over the same memory, which fails with device-only barriers; the rest of make -C dm/gfx/tests run as on main. Ocarina of Time played to Kokiri Forest with a correct picture, with this and the resolution family change together on main at 2eb7182 (not rerun on this base, b2d46b8). No other game was run. frame-rate.md entry, symptom index, env_switches and the game's note updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
seedds
added a commit
that referenced
this pull request
Nov 7, 2026
…rs carry the resource-alias flush: Ocarina of Time's main submission's command allocators no longer grow to 36-80 MB each (PR #1556) - Symptom: CPU-visible Metal memory (footprint's IOAccelerator (graphics)) of several hundred MB in thousands of 32-48 KB regions, most of it the 8 command allocators of the main submission. On Ocarina of Time (0400486024DEC000) before main's Ocarina fixes, scenes of about 150 passes a frame were slow while the GPU was not the limit. - Cause: while overlapping buffers or aliasing textures exist (large-pool chunks, image family heaps), resource_vis() adds MTL4VisibilityOptionResourceAlias, and m4_end put it on every encoder's end barrier (barrierAfterStages:beforeQueueStages:) as well as on its start barrier. On the end barrier it costs the command allocator about 100 KB per encoder; on the start barrier nothing measurable. An allocator keeps the size of the largest command buffer it held after a reset, so submissions of 600-1,400 encoders left all 8 at that size. - Fix: m4_end uses MTL4VisibilityOptionDevice. Every encoder the host opens on the main command buffer starts with a barrier carrying resource_vis() after all earlier work (draw passes, enc_r_begin, enc_c_begin, blit_open, the scaler's before MetalFX), so an encoder's writes are flushed for aliases before a later encoder reads them. A copy encoder that wrote through an aliased buffer address (g_blit_alias) keeps the flush on its end barrier. DM_NVN_END_ALIAS=1 brings the end flush back (ab alternates it with DM_AB). - DM_NVN_ALLOCPROF=2 gives each main submission a fresh allocator and reports command memory by operation, by encoder, by submission size, and the indirect-count multi-draws encoded. - Measured on an M3 Pro (18 GB): a test program's 600 render passes of 4 draws took 27.7 MB of command memory with the flush on start barriers only, 88.5 MB with it on end barriers too; device-only barriers on both sides read stale data through a second buffer over the same memory. Ocarina of Time in Kokiri Forest before main's Ocarina fixes: CPU-visible Metal memory about 800 -> 548 MB, frames 18-30 ms at p50 without the end flush against 31-45 ms with it (DM_NVN_END_ALIAS=ab). On main at 2eb7182: 392 -> 298 MB, the 8 allocators 181 -> 116 MB, no frame-time difference (A/B phase pairs within 2 ms). - Checked: check_t_copy_alias (126 checks, both encoding modes) with a new case, a shader writing through one no-copy buffer and the next encoder's shader reading through another over the same memory, which fails with device-only barriers; the rest of make -C dm/gfx/tests run as on main. Ocarina of Time played to Kokiri Forest with a correct picture, with this and the resolution family change together on main at 2eb7182 (not rerun on this base, b2d46b8). No other game was run. frame-rate.md entry, symptom index, env_switches and the game's note updated. - Landed on 7fe9fc4 with #1556 and #1557 together: the host builds; t_copy_alias, t_image_alias, t_pool_chunks, t_alias_takeover and t_rt_range pass. No game was run and scripts/regress.py was skipped. Co-authored-by: James <12531452+capibawa@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
seedds
added a commit
that referenced
this pull request
Nov 7, 2026
… only COMPRESSIBLE|IMAGE: Ocarina of Time's dynamic-resolution depth buffer (COMPRESSIBLE alone, in a virtual pool) kept 15-16 live sizes at one offset as separate 14.5 MB Metal textures (PR #1557) - Symptom: in Kokiri Forest on an M3 Pro (18 GB), 15-16 Depth32Float textures of 1,882-1,920 pixels described one pool offset, each with an allocation of its own. - Cause: image_shape required the flags to be exactly COMPRESSIBLE|IMAGE. A depth target carries COMPRESSIBLE alone, so its resolution family never formed. - Fix: image_shape takes any COMPRESSIBLE image without DISPLAY, VIDEO_DECODE, LINEAR, SPARSE or LINEAR_RENDER_TARGET; the other conditions (GPU-only mapped storage, 2D, one level, one sample, same pool, offset and format) are unchanged. - DM_CTL restop also prints a memory census (mem_census): textures by Metal format; which ones live descriptors, only retired ones, only the deferred-release queue or nothing names; storage addresses held as several Metal textures, with their texture and pool flags; memory pools by whether texture uploads or other uses looked them up. It is what found these textures. - Measured, before main's Ocarina fixes: Depth32Float 95 textures (458 MB) to 60 (191 MB) plus 21 family heaps (82 MB); GPU allocation 7,710 to 7,520 MB. On main at 2eb7182 the 17 sizes still form and share one heap, all Depth32Float textures 225 MB. - PR #1453's per-pool placement heaps leave virtual pools out, so they do not take this buffer. - Checked: check_t_image_alias (332 checks, direct and deferred encoding) with a new depth family case (Depth32F, COMPRESSIBLE alone) and a linear-exclusion case; the rest of make -C dm/gfx/tests run as on main; Ocarina of Time played to Kokiri Forest with a correct picture, with this and the end-barrier change together on main at 2eb7182 (not rerun on this base, b2d46b8). rendering.md entry and the game's note updated. - Landed on 7fe9fc4 with #1556 and #1557 together: the host builds; t_copy_alias, t_image_alias, t_pool_chunks, t_alias_takeover and t_rt_range pass. No game was run and scripts/regress.py was skipped. Co-authored-by: James <12531452+capibawa@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Landed as 5b84d37, unchanged apart from a one-line conflict in Ocarina of Time's note (you are credited as co-author). I checked the premise on current main: every encoder the host opens (draw passes, compute, copies, uploads on the same queue, the scaler, present, snapshots) begins with a barrier that carries |
2 tasks done
gnosticgeek
pushed a commit
to gnosticgeek/nxforge
that referenced
this pull request
Nov 7, 2026
…merges clang-tidy found one more core.NullDereference in nvn_mtl.m (tex_cpu_content's first-use log line read t->pool->flags without a pool, seedds#1604), and one more nullability.NullablePassedToNonnull each in t_copy_alias.m (a kernel function looked up by name, seedds#1556) and t_pso_archive.m (the archive path from a boxed C string, seedds#1307). The log line prints 0 for a texture without a pool; the tests stop when the lookup fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK6QbhFrzr2Vop9t34k
thomasruegsegger
pushed a commit
to thomasruegsegger/nxnative
that referenced
this pull request
Nov 7, 2026
…s first write stays resident while any of them holds it: releasing one took it out of the residency set (GPU page faults, issue seedds#1326) The serials of one storage share one packaged texture (g_pkg_share). At the GPU's first write over that storage, cpu_unc_drop gives each of them the package's texture itself (tex_put), so they all hold one texture that was in no shared set. The first holder released (tex_put's ghost-ring eviction, serial_put or serial_del, through obj_drop or defer_obj_release), or the package's own release (NvPkg dealloc's res_remove), took it out of the residency set while the other serials still drew with it, and a command buffer reading it faulted. pkg_hand_over enters the handed-over texture in g_alias_set and in g_handed, a list by the package's sharing key, so releasing one holder only drops a reference (alias_has). The package's release and its re-upload's spare leave a handed-over texture resident, mtl_alias_drop_pool retires it with its pool, and tex_upgrade moves its entry. It does not enter g_alias, so a new texture object over the same storage still gets a texture of its own. DM_UNC_SHARE=0 gives back the old hand-over. Ocarina of Time v131072, Mac mini M4 16 GB, macOS 27.2, on main b2d46b8 with seedds#1590, seedds#1557, seedds#1556, seedds#1575, seedds#1522, seedds#1543 and seedds#1390: page faults at 44, 29, 24 and 15.5 minutes, with the missing textures named by DM_RES_CHECK and a diagnostic of where each left the residency set, before two of them, as 260x260 terrain tiles handed over this way and released by the ghost-ring eviction. One binary: with the change, 107 minutes and no fault (86 hand-overs); with DM_UNC_SHARE=0, a fault at 37 minutes with the same kind of tiles named. t_cpu_tex --metal handover_shared: 97 checks, 0 failed; with DM_UNC_SHARE=0, 2 failed. The other GPU suites are unchanged. (cherry picked from commit 8cbc330)
Know-It-All-Brother
pushed a commit
to Know-It-All-Brother/nxnative
that referenced
this pull request
Nov 9, 2026
…rs carry the resource-alias flush: Ocarina of Time's main submission's command allocators no longer grow to 36-80 MB each (PR seedds#1556) - Symptom: CPU-visible Metal memory (footprint's IOAccelerator (graphics)) of several hundred MB in thousands of 32-48 KB regions, most of it the 8 command allocators of the main submission. On Ocarina of Time (0400486024DEC000) before main's Ocarina fixes, scenes of about 150 passes a frame were slow while the GPU was not the limit. - Cause: while overlapping buffers or aliasing textures exist (large-pool chunks, image family heaps), resource_vis() adds MTL4VisibilityOptionResourceAlias, and m4_end put it on every encoder's end barrier (barrierAfterStages:beforeQueueStages:) as well as on its start barrier. On the end barrier it costs the command allocator about 100 KB per encoder; on the start barrier nothing measurable. An allocator keeps the size of the largest command buffer it held after a reset, so submissions of 600-1,400 encoders left all 8 at that size. - Fix: m4_end uses MTL4VisibilityOptionDevice. Every encoder the host opens on the main command buffer starts with a barrier carrying resource_vis() after all earlier work (draw passes, enc_r_begin, enc_c_begin, blit_open, the scaler's before MetalFX), so an encoder's writes are flushed for aliases before a later encoder reads them. A copy encoder that wrote through an aliased buffer address (g_blit_alias) keeps the flush on its end barrier. DM_NVN_END_ALIAS=1 brings the end flush back (ab alternates it with DM_AB). - DM_NVN_ALLOCPROF=2 gives each main submission a fresh allocator and reports command memory by operation, by encoder, by submission size, and the indirect-count multi-draws encoded. - Measured on an M3 Pro (18 GB): a test program's 600 render passes of 4 draws took 27.7 MB of command memory with the flush on start barriers only, 88.5 MB with it on end barriers too; device-only barriers on both sides read stale data through a second buffer over the same memory. Ocarina of Time in Kokiri Forest before main's Ocarina fixes: CPU-visible Metal memory about 800 -> 548 MB, frames 18-30 ms at p50 without the end flush against 31-45 ms with it (DM_NVN_END_ALIAS=ab). On main at 077fb5c: 392 -> 298 MB, the 8 allocators 181 -> 116 MB, no frame-time difference (A/B phase pairs within 2 ms). - Checked: check_t_copy_alias (126 checks, both encoding modes) with a new case, a shader writing through one no-copy buffer and the next encoder's shader reading through another over the same memory, which fails with device-only barriers; the rest of make -C dm/gfx/tests run as on main. Ocarina of Time played to Kokiri Forest with a correct picture, with this and the resolution family change together on main at 077fb5c (not rerun on this base, 99edc18). No other game was run. frame-rate.md entry, symptom index, env_switches and the game's note updated. - Landed on 622391d with seedds#1556 and seedds#1557 together: the host builds; t_copy_alias, t_image_alias, t_pool_chunks, t_alias_takeover and t_rt_range pass. No game was run and scripts/regress.py was skipped. Co-authored-by: James <12531452+capibawa@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Know-It-All-Brother
pushed a commit
to Know-It-All-Brother/nxnative
that referenced
this pull request
Nov 9, 2026
… only COMPRESSIBLE|IMAGE: Ocarina of Time's dynamic-resolution depth buffer (COMPRESSIBLE alone, in a virtual pool) kept 15-16 live sizes at one offset as separate 14.5 MB Metal textures (PR seedds#1557) - Symptom: in Kokiri Forest on an M3 Pro (18 GB), 15-16 Depth32Float textures of 1,882-1,920 pixels described one pool offset, each with an allocation of its own. - Cause: image_shape required the flags to be exactly COMPRESSIBLE|IMAGE. A depth target carries COMPRESSIBLE alone, so its resolution family never formed. - Fix: image_shape takes any COMPRESSIBLE image without DISPLAY, VIDEO_DECODE, LINEAR, SPARSE or LINEAR_RENDER_TARGET; the other conditions (GPU-only mapped storage, 2D, one level, one sample, same pool, offset and format) are unchanged. - DM_CTL restop also prints a memory census (mem_census): textures by Metal format; which ones live descriptors, only retired ones, only the deferred-release queue or nothing names; storage addresses held as several Metal textures, with their texture and pool flags; memory pools by whether texture uploads or other uses looked them up. It is what found these textures. - Measured, before main's Ocarina fixes: Depth32Float 95 textures (458 MB) to 60 (191 MB) plus 21 family heaps (82 MB); GPU allocation 7,710 to 7,520 MB. On main at 077fb5c the 17 sizes still form and share one heap, all Depth32Float textures 225 MB. - PR seedds#1453's per-pool placement heaps leave virtual pools out, so they do not take this buffer. - Checked: check_t_image_alias (332 checks, direct and deferred encoding) with a new depth family case (Depth32F, COMPRESSIBLE alone) and a linear-exclusion case; the rest of make -C dm/gfx/tests run as on main; Ocarina of Time played to Kokiri Forest with a correct picture, with this and the end-barrier change together on main at 077fb5c (not rerun on this base, 99edc18). rendering.md entry and the game's note updated. - Landed on 622391d with seedds#1556 and seedds#1557 together: the host builds; t_copy_alias, t_image_alias, t_pool_chunks, t_alias_takeover and t_rt_range pass. No game was run and scripts/regress.py was skipped. Co-authored-by: James <12531452+capibawa@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rafaeliga
pushed a commit
to rafaeliga/kid-with-sword-remake
that referenced
this pull request
Nov 11, 2026
…rs carry the resource-alias flush: Ocarina of Time's main submission's command allocators no longer grow to 36-80 MB each From nxnative main 5b84d37 (seedds, PR seedds#1556): scenes of about 150 passes a frame were slow while the GPU was not the limit, and CPU-visible Metal memory held several hundred MB of command allocators. Checked: check_t_copy_alias (126), check_t_res_guard, check_t_tex_recycle. Co-authored-by: seedds <seedds@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Symptom: CPU-visible Metal memory runs to several hundred MB, spread over thousands of 32–48 KB regions (the
IOAccelerator (graphics)row offootprint). Most of it belongs to the main submission's 8 command allocators:DM_NVN_ALLOCPROF=1shows them at 36–80 MB each in Ocarina of Time (0400486024DEC000) before main's Ocarina fixes.Cause: while overlapping buffers or aliasing textures exist (large-pool chunks, image family heaps),
resource_vis()addsMTL4VisibilityOptionResourceAlias.m4_endput that flag on every encoder's end barrier (barrierAfterStages:beforeQueueStages:) as well as its start barrier.Change:
m4_endnow usesMTL4VisibilityOptionDevice.resource_vis(). That covers the draw passes,enc_r_begin,enc_c_begin,blit_open, and the scaler's barrier before MetalFX. So an encoder's writes are still flushed for aliases before any later encoder reads them.g_blit_alias) keeps the flush on its end barrier, as Buffer copies through aliased Metal addresses needs.DM_NVN_END_ALIAS=1brings the end-barrier flush back.DM_NVN_END_ALIAS=abalternates it with theDM_ABphases, for comparing the two.DM_NVN_ALLOCPROF=2gives each main submission a fresh allocator. It then reports command memory by operation, by encoder and by submission size, plus the indirect-count multi-draws encoded.Docs: a new reference entry, The alias flush on every encoder's end barrier (frame-rate.md). Also the symptom index,
env_switches.tsv, and a Found on line in Ocarina of Time's note.Nothing in the open PRs touches these barriers. #1557, the resolution families change, is separate. Both PRs add a line at the end of Ocarina of Time's "Found on this game", so whichever merges second has a one-line conflict.
Checked
make -C dm/gfx/tests GAME=0400486024DEC000 check_t_copy_alias: 126 checks pass, with direct and deferred encoding. The new case has a shader write through one no-copy buffer and the next encoder's shader read through another buffer over the same memory, while overlapping buffers exist. It fails with device-only barriers on both sides and passes with the flush on the start barrier alone.make -C dm/gfx/tests GAME=0400486024DEC000 run, each check run separately, matchesmainat b2d46b8:t_alpha,t_logicandt_fuse_checkfail onmaintoo. Their Python usesX | None, which the system Python 3.9 can't parse.t_nvnandt_gfx_suspenddon't link onmain(_dm_vi_frame_queued).dm/tools/tests/test_docs.pypasses.mainat 2eb7182. I haven't rerun the game on this base (b2d46b8).regress.pywas not run (the user asked me not to), and no other game was played.regress.py check --gameson a few games with large pools or image families before merging would be worthwhile.DM_NVN_END_ALIAS=ab DM_AB=600, scenes of about 150 passes took 18–30 ms a frame at p50 without the end flush, against 31–45 ms with it.mainat 2eb7182 (about 95 passes a frame):Docs
docs/reference/frame-rate.mdentry and the game note, andpython3 dm/tools/docs_symptoms.pywas run (the symptom index)🤖 Generated with Claude Code