Skip to content

Zelda: Ocarina of Time — Restore missing mesh and texture effects on Mac - #1312

Closed
nanotchec wants to merge 2 commits into
seedds:mainfrom
nanotchec:codex/oot-metal-mesh
Closed

nanotchec wants to merge 2 commits into
seedds:mainfrom
nanotchec:codex/oot-metal-mesh

Conversation

@nanotchec

Copy link
Copy Markdown
Contributor

Ocarina of Time 1.1.1 on Switch 2 reaches Kokiri Forest on Mac, but its opening uses two Metal replay paths that were missing: indirect task/mesh draws and integer image atomics through a color texture view. This PR supplies those paths and removes a resource retirement bottleneck seen while entering the forest.

  • Convert the two GPU-written NVN mesh arguments into a Metal indirect grid and a first-task value. Land pending inline writes before caching the conversion, and preserve zero-work draws.
  • For eligible RGBA8 textures bound as R32 integer atomic images, use R32 storage with an RGBA8 view of the same bytes. Reads, ordinary rendering and integer atomics keep seeing the same contents.
  • Partition ready and pending deferred releases in one pass. The old loop repeatedly moved the pending tail of the array during the forest load.

This is a renderer follow-up for #1299 and #1297. It keeps their game profile and shader translation work independent; the patch applies cleanly alongside the current head of #1299. It does not change the shader chain.

Validation

  • Native Metal fixtures check GPU-written indirect arguments, zero work, and a deferred write; read/write/atomic aliasing (50 checks); and ordering and lifetime in a 100,000-entry deferred queue.
  • Tested the renderer as part of the full 1.1.1 bring-up on an Apple M3 Max, macOS 27.0.1, Xcode 27.2: the French opening, movement and the house exit in Kokiri Forest, plus save creation and reload. This is opening-route coverage, not a full playthrough.
  • The full bring-up branch compiled all 42,853 Ocarina shaders. A static regression against its upstream base compared 9,698 Link’s Awakening shaders with no new failures, generated Metal changes or interface differences. No other game builds were available locally.
  • The signed 1.1.1 Mac app reloaded an outdoor Kokiri Forest autosave and executed an indirect mesh draw. Its bundled pipeline archive passed all 15 base probes. The renderer-only branch passed the native fixtures, documentation checks, and C lint with no new findings.

@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation labels Nov 5, 2026
@github-actions github-actions Bot added the tools Tools and scripts (dm/tools, scripts) label Nov 5, 2026
fpedrolucas95 pushed a commit to fpedrolucas95/nxnative that referenced this pull request Nov 5, 2026
@lunks

lunks commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

With DM_NVN_ENC_THREAD=1, this PR's indirect mesh draw (nvn_geometry.inc calls drawMeshThreadgroupsWithIndirectBuffer: on the encoder) stops the host at the first one: FATAL deferred encoding: DmRecEnc has no stand-in for -drawMeshThreadgroupsWithIndirectBuffer:threadsPerObjectThreadgroup:threadsPerMeshThreadgroup:. Two reports saw it: @PaulEins on a tree with this PR (#1265, on an M4 Pro, where a four-line stand-in made it run), and @fumeware (#1326: with the encode thread on, the game crashes at drawMeshThreadgroupsWithIndirectBuffer).

The stand-in is four lines in dm/gfx/nvn/nvn_rec.h, plus a check in t_mesh_indirect.m. It records the draw as RO_R_MESH_IND and the encoding thread replays it, as it does RO_R_MESH:

-    RO_R_DRAW, RO_R_DRAW_INST, RO_R_DRAW_IND, RO_R_DRAW_IDX, RO_R_DRAW_IDX_IND, RO_R_MESH, RO_R_STORE_C, RO_R_STORE_D, RO_R_STORE_S,
+    RO_R_DRAW, RO_R_DRAW_INST, RO_R_DRAW_IND, RO_R_DRAW_IDX, RO_R_DRAW_IDX_IND, RO_R_MESH, RO_R_MESH_IND, RO_R_STORE_C, RO_R_STORE_D, RO_R_STORE_S,
+    case RO_R_MESH_IND: [(id<MTL4RenderCommandEncoder>)e drawMeshThreadgroupsWithIndirectBuffer:a[0] threadsPerObjectThreadgroup:MTLSizeMake(a[2] ? a[2] : 1, 1, 1) threadsPerMeshThreadgroup:MTLSizeMake(a[1], 1, 1)]; break;
+- (void)drawMeshThreadgroupsWithIndirectBuffer:(uint64_t)b threadsPerObjectThreadgroup:(MTLSize)o threadsPerMeshThreadgroup:(MTLSize)m { rec_t *r = rec_put(RO_R_MESH_IND, 0); r->a[0] = b; r->a[1] = m.width; r->a[2] = o.width; }
+    // DM_NVN_ENC_THREAD records encoders: an indirect mesh draw needs its stand-in, or the first one stops the host (deferred encoding: DmRecEnc has no stand-in)
+    assert([DmRecEnc instancesRespondToSelector:@selector(drawMeshThreadgroupsWithIndirectBuffer:threadsPerObjectThreadgroup:threadsPerMeshThreadgroup:)]);
+    puts("the encoding thread records an indirect mesh draw PASS");

On our side, with these lines on top of this PR, Ocarina of Time booted headless with DM_NVN_ENC_THREAD=1 from a save, with the game's upscaler and with MetalFX temporal, and reached gameplay where it stopped before. The commit is lunks@6a18b7b2 (branch pr/enc-thread-mesh-indirect, on top of this PR's commits). Happy for you to take it into this PR, or to open it separately.

@seedds

seedds commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Thank you for the work and testing. The game now reaches and plays Kokiri Forest on main (landed on main in b1d7c70 and 6121fd7), including integer atomics through a colour texture, so I'm closing this. Your diagnosis was useful. Final checks on the rebased tree (full shader build, walks, regression) are still running, so please report anything you see.

@Romen-Adama-Dev

Copy link
Copy Markdown
Contributor

Applied on top of #1299 merged onto main 865cc35f (plus #1345, #1373), on an Apple M3 Pro (18 GB), macOS 27.0.1, update v131072: the [RED] image atomics on a texture Metal cannot run them on … they go to a write sink line is gone and mesh pipelines run in Kokiri Forest (task / mesh pipeline: task - mesh 0c1ffecd02018fb0_b fragment 7ad8843afe105a32_b). The overexposure in the forest is unchanged, though: the scene with Saria is nearly white, so whatever drives the exposure is not among what this PR restores (two compute shaders dispatched at the world load still fail to translate here: 83df46a0b1b379b4, 8692b34b521225be, Ampere instructions 0x3a9 / 0x243 / 0x481; they may be the ones). The 3 Foliage mesh shaders with ISBERD mode 0xb8 also fail with the merged translator, so the full branch did not compile all 42,853 on this machine.

gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
) 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
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) tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants