Skip to content

nvn: reactive memory watchdog, 16-way view cache, and texture upload batching - #2008

Open
bquilty25 wants to merge 1 commit into
seedds:mainfrom
bquilty25:perf/vc-ways-and-mem-scaling
Open

bquilty25 wants to merge 1 commit into
seedds:mainfrom
bquilty25:perf/vc-ways-and-mem-scaling

Conversation

@bquilty25

Copy link
Copy Markdown

What and why

Three performance and stability improvements to NVN memory handling and caching:

  1. Reactive Host Memory Watchdog (hle_nvn.c):

    • DM_MEM_CAP_DEFAULT_GB was hardcoded to 24 GB for macOS hosts. On a 16 GB Mac, resident memory climbing towards 24 GB leads to kernel paging/pressure before the watchdog fires; on a 64 GB or 128 GB Mac, it arbitrarily kills processes with plenty of physical memory headroom.
    • Sizes the default limit dynamically from hw.memsize: leaving 16 GB headroom on 64+ GB Macs (48 GB limit), 8 GB on 36–48 GB Macs (28–40 GB limit), and 4 GB on 16 GB Macs (12 GB limit). DM_MEM_CAP_GB still overrides.
  2. 16-Way Texture View Cache (VC_WAYS = 16 in nvn_mtl.m):

    • Upstream VC_WAYS = 4 suffers from severe thrashing in games with high texture view churn. In The Legend of Zelda: Ocarina of Time (0400486024DEC000), a 7-minute run created over 901,800 texture views on-the-fly (~1,800/sec).
    • Raising VC_WAYS to 16 reduced view creations from 901,825 to 4 (99.8% cache hit rate), eliminating view allocation overhead on the submit thread.
  3. Batched Texture Uploads Outside Render Command Buffers (nvn_mtl.m):

    • When !g_cb, direct and staging texture uploads (pkg_upload_) called up_flush() for every single texture. When streaming an area with 100+ textures (e.g. Kokiri Forest transition), this queued dozens of individual command buffer commits in series, causing submit stalls of 200+ ms.
    • Grouping them in batches of 32 (g_direct_uploads % 32 == 0, g_up_texs.count >= 32) cuts streaming spikes by over 80%. All pending uploads are still guaranteed to flush before submissions commit or begin render passes (mtl_sub_begin, res_commit).

Checked

  • Builds: make -C dm/host GAME=0400486024DEC000 dmhost clean.
  • Tests: python3 -m unittest dm/tools/tests/test_docs.py, python3 -m unittest dm/tools/tests/test_env_switches.py, python3 dm/tools/docs_symptoms.py --check all pass.
  • In-game: The Legend of Zelda: Ocarina of Time (v131072) on Apple Silicon:
    • Memory watchdog correctly initialized to 48 GB on a 64 GB host: [dm] [mem] reactive memory watchdog limit: 48 GB.
    • Texture view reuse: 193 views reused, 4 made in total (down from ~900k views created).
    • Sustained 60 FPS with zero dropped frames.

- Size default memory watchdog limit dynamically from hw.memsize on Mac
  (48 GB on 64 GB, 28-40 on 36-48 GB, 12 on 16 GB; DM_MEM_CAP_GB overrides)
- Raise texture view cache associativity from 4 to 16 ways (VC_WAYS = 16),
  preventing view thrashing in games with high view turnover
- Group direct and staging texture uploads in batches of 32 when outside
  render command buffers, avoiding per-texture synchronous flush freezes
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation labels Nov 9, 2026
FeReuss pushed a commit to FeReuss/nxnative-oot that referenced this pull request Nov 10, 2026
…hdog, 16-way view cache, and texture upload batching

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
FeReuss pushed a commit to FeReuss/nxnative-oot that referenced this pull request Nov 10, 2026
…batched uploads

The PR sized the memory watchdog's default from host RAM (12 GB on a 16 GB Mac, down from 24). The watchdog exits the game, and Ocarina of Time is resident at 10.9-11.4 GB on a 16 GB Mac (game note), so the new cap would end runs that used to continue. DM_MEM_CAP_GB still overrides either way.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fasen24-AI added a commit to Fasen24-AI/swordboiMacNative that referenced this pull request Nov 10, 2026
seedds#1901 seedds#1459 seedds#1928 seedds#1942 seedds#1940 seedds#1952 seedds#1611 seedds#2065 seedds#2083 seedds#2008 seedds#2079 seedds#2022 seedds#2075 seedds#2077 seedds#2020 seedds#2021 onto main 04811a6

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7

Upstream pull requests applied in this commit (their authors' work, brought onto this tree with a three-way merge; the description is in each PR):
- seedds#1459 by PromethOps (open): DM_UPSCALER=metalfx takes agl's agl_ndsr_vr.pck networks too, and a variant with no curve and no power is drawn as one colour space: Ocarina of Time's upscaler is MetalFX's
- seedds#1611 by Esteban-V (open): An argument block whose only change is a uniform buffer is patched instead of encoded again
- seedds#1774 by uncraftablexd (closed): Compute threads that exit before a barrier keep arriving at it, and every shader loop has a cap (GPU hang defences), chain change
- seedds#1824 by cocoman93 (open): Clamp texel fetches past a sampled 2D texture's edge: no hard edge in Ocarina of Time's mist
- seedds#1901 by FifteenPen (open): The game's compute dispatches with no game barrier between them run side by side (profile key dispatch_overlap, on for Ocarina of Time): Kokiri Forest GPU p50 15.63 → 14.76 ms
- seedds#1928 by FeReuss (open): Ocarina of Time: enc_thread on, the replay encodes on a thread of its own
- seedds#1929 by FeReuss (open): Unused border corrections are compiled out of pipelines that bind no sampler with a twin (function constant DMSH_BORDER_FC)
- seedds#1930 by FeReuss (open): A float add to storage memory with no result read is one native atomic add instead of a compare-and-swap loop
- seedds#1940 by uncraftablexd (open): A prewarm list's lines after its #@after_first_frame line are built after the first frame, in the background, instead of in the prepare step (issue seedds#1747)
- seedds#1942 by uncraftablexd (open): Frame step: late frames with the GPU idle do not step up, and a longer hold the GPU does not explain is probed
- seedds#1952 by AdyaGMD (open): Compute moves ahead of scene passes past occlusion queries and conditional rendering; clears wait across dispatches
- seedds#2008 by bquilty25 (open): nvn: reactive memory watchdog, 16-way view cache, and texture upload batching
- seedds#2020 by padsiwono (open): Ocarina of Time v131072: Output Native draws the full output size instead of the game's 80 % frame (`DM_OUTPUT`)
- seedds#2021 by padsiwono (open): Ocarina of Time v131072 renders at the launcher's Resolution (`DM_RENDER_SIZE`), docked
- seedds#2022 by sleepyeyedjoe (closed): Ocarina of Time: large models no longer go black for one frame twice, 4–5 frames apart, when the game streams new textures into them (a texture ID a shader has read keeps its read mark when it gets another texture)
- seedds#2030 by humanperzeus (open): A NaN colour at alpha 0 is returned as 0, as NVIDIA's blender takes a zero factor: Ocarina of Time's white clouds over the ferns are gone
- seedds#2035 by Know-It-All-Brother (open): The shader build runs as worker processes, and no shader waits for a batch (Ocarina of Time's cold Mac map: 3,002 s to 1,298 s)
- seedds#2065 by Raymon82777 (closed): A vertex library with layer entry points of its own gets bases for its main0 pairs: Ocarina of Time's 230 ms camera-turn hitch in Hyrule Field goes to 39 ms
- seedds#2075 by sleepyeyedjoe (closed): Ocarina of Time: a patch of scenery no longer draws dark for one frame when the game gives it a sharper texture (a pool texture the walk does not make holds a stand-in of its own colours until its first read)
- seedds#2077 by roandegraaf (closed): Ocarina of Time: fix the "plaid" terrain / missing texture tiles (GPU-drawn textures now reach the storage the game copies)
- seedds#2079 by uncraftablexd (open): Render pipelines that share a vertex function are taken from the archive or compiled one at a time: the AGX driver crashes in createVertexProgramVariant's completion block
- seedds#2083 by FifteenPen (closed): Ocarina of Time's prewarm list gets 41 more task / mesh pipelines from later play
@roandegraaf

Copy link
Copy Markdown
Contributor

Two parts of this overlap open PRs. They merge cleanly with #2260, but they should be measured together:

The memory watchdog part doesn't overlap anything I know of.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation 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