Skip to content

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
seedds:mainfrom
capibawa:pr-depth-family

Conversation

@capibawa

@capibawa capibawa commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

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_texture let live descriptions at one offset share a placement heap (Live resolution variants share physical image storage). But image_shape only accepted textures whose flags are exactly COMPRESSIBLE|IMAGE. A depth target carries COMPRESSIBLE alone, so its family never formed.

Change: image_shape now takes any COMPRESSIBLE image that has none of DISPLAY, VIDEO_DECODE, LINEAR, SPARSE or LINEAR_RENDER_TARGET. Every other condition is unchanged: GPU-only mapped storage, 2D, one level, one sample, same pool, offset and format.

DM_CTL restop also prints a memory census, mem_census (diagnostic only):

  • textures by Metal format;
  • which textures are named by live descriptors, only by retired ones, only by the deferred-release queue, or by nothing;
  • storage addresses held as several Metal textures, with their texture and pool flags;
  • memory pools, by whether they were looked up by texture uploads or by other uses.

The census is what found these textures.

Relation to open PRs: #1453 (per-pool placement heaps) skips VIRTUAL pools. This buffer's pool has flags 0x2a1, which includes VIRTUAL, so #1453 does not cover it. If both land:

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, COMPRESSIBLE alone, at a 12 MB offset) and a linear texture that stays out of its family.
    • The rest of make -C dm/gfx/tests GAME=0400486024DEC000 run, each check run separately, matches main at b2d46b8:
      • t_alpha, t_logic and t_fuse_check fail on main too. Their Python uses X | None, which the system Python 3.9 can't parse.
      • t_nvn and t_gfx_suspend don't link on main (_dm_vi_frame_queued).
    • dm/tools/tests/test_docs.py passes.
  • Games run:

  • Device and OS: M3 Pro (18 GB), macOS 27.0.1.

  • Measurements: both from Ocarina of Time in Kokiri Forest on the M3 Pro.

    Base Before After
    Before main's Ocarina fixes Depth32Float: 95 textures, 458 MB; GPU allocation 7,710 MB Depth32Float: 60 textures (191 MB) plus 21 family heaps (82 MB); GPU allocation 7,520 MB
    main at 2eb7182 the 17 sizes share one heap; all Depth32Float textures take 225 MB

Docs

  • The finding is in its game note and its docs/reference/rendering.md entry. The symptom line is unchanged, so python3 dm/tools/docs_symptoms.py doesn't change the index.
  • No game files, keys or data extracted from a game in the change or the attachments

🤖 Generated with Claude Code

… 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>
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation game: 0400486024DEC000 A game labels Nov 6, 2026
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>
@seedds

seedds commented Nov 7, 2026

Copy link
Copy Markdown
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 regress.py was skipped. #1587 stays open: the render targets that move and resize in the virtual pool need the pool-heap design, keyed by physical storage. Thank you.

@seedds seedds closed this Nov 7, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400486024DEC000 A game 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.

2 participants