Repository navigation
Resolution families take every GPU-written compressible 2D image, not 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 - #1557
Closed
capibawa wants to merge 1 commit into
Closed
capibawa wants to merge 1 commit into
capibawa wants to merge 1 commit into
Conversation
… 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 - 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 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 2eb7182 (not rerun on this base, b2d46b8). rendering.md entry and the game's note updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aznracer
pushed a commit
to Aznracer/Mighty-Green-Kokiri-Kid
that referenced
this pull request
Nov 6, 2026
… only COMPRESSIBLE|IMAGE: Ocarina of Time's dynamic-resolution depth buffer kept 15-16 live sizes as separate 14.5 MB textures (from PR seedds#1557's b5cedcb, without its memory census)
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 863bc6b, on top of #1556 (5b84d37), which removes most of the cost of the extra heaps (the end-barrier alias flush). It is unchanged apart from a one-line conflict in the game note, and you are credited as co-author. On 7fe9fc4 the host builds and t_image_alias (332) and the alias tests pass. No game was run here and |
2 tasks done
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>
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: Ocarina of Time (0400486024DEC000) has a dynamic-resolution depth buffer. In Kokiri Forest it kept 15–16 live sizes at one pool offset, each a separate Metal texture of 14.5 MB. The sizes are Depth32Float, 1,882–1,920 pixels wide.
Cause: the resolution families in
image_texturelet live descriptions at one offset share a placement heap (Live resolution variants share physical image storage). Butimage_shapeonly accepted textures whose flags are exactlyCOMPRESSIBLE|IMAGE. A depth target carriesCOMPRESSIBLEalone, so its family never formed.Change:
image_shapenow takes anyCOMPRESSIBLEimage that has none ofDISPLAY,VIDEO_DECODE,LINEAR,SPARSEorLINEAR_RENDER_TARGET. Every other condition is unchanged: GPU-only mapped storage, 2D, one level, one sample, same pool, offset and format.DM_CTL restopalso prints a memory census,mem_census(diagnostic only):The census is what found these textures.
Relation to open PRs: #1453 (per-pool placement heaps) skips
VIRTUALpools. This buffer's pool has flags0x2a1, which includesVIRTUAL, so #1453 does not cover it. If both land:image_textureruns beforepool_heap_texture, so in a non-virtualCPU_NO_ACCESSpool, a depth family would take its textures first. That already happens today forCOMPRESSIBLE|IMAGEfamilies.pool_heapstill keeps a family from forming over textures already placed in a pool heap.Docs: the rendering.md entry's eligibility, Check and Found on text, and a Found on line in Ocarina of Time's note.
Checked
Tests:
make -C dm/gfx/tests GAME=0100DCA0064A6000 check_t_image_alias: 332 checks pass, with direct and deferred encoding. Two cases are new: a depth family (Depth32F,COMPRESSIBLEalone, at a 12 MB offset) and a linear texture that stays out of its family.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.Games run:
mainat 2eb7182, with a full rebuild for that chain. I haven't rerun the game on this base (b2d46b8).regress.pywas not run, on purpose: the user asked me not to. Games with compressible depth or render targets in GPU-only pools are the ones this reaches.Device and OS: M3 Pro (18 GB), macOS 27.0.1.
Measurements: both from Ocarina of Time in Kokiri Forest on the M3 Pro.
mainat 2eb7182Docs
docs/reference/rendering.mdentry. The symptom line is unchanged, sopython3 dm/tools/docs_symptoms.pydoesn't change the index.🤖 Generated with Claude Code