Repository navigation
NVN: account for four-byte direct indexed draws using control memory - #1609
ishtiaq156 wants to merge 12 commits into
Conversation
(cherry picked from commit 82c2fa6b14e5d1f6f45f0d298ead980f5a578425)
(cherry picked from commit 9e21a6fb486d36c1601257569e7c2306a464fc49)
Keep the upstream asynchronous queue/conditional-state scheduler and seedds#1609 four-byte command tickets together. Queue flatten resolves direct and nested four-byte handles under the ticket lifetime read lock, copying canonical DRAW_IDX+END into queue-owned memory before deferred replay or guest regrant. Unrelated titles remain default-off. Add a native-minimum 512-byte CPU-only queue snapshot red/green, with previous ticket-race, generation, defensive small-grant and capture checks preserved. Validation: expected RED exit134 before queue adaptation; green focused ticket/capture exit0 (including t_capture 101 checks), scheduler queue lifetime 38/38 exit0, render_cond-on continuation 38/38 exit0. No game launch, ROM, RESTBL, shader or signed app changes. The separate DM_NVN_BC_ALIAS environment-registry defect is upstream baseline, unrelated to this PR.
|
A second game that needs this: Drag x Drive ( Without the four-byte tickets it crashes 5 s after boot, on its first scene: the game initializes a buffer in no memory pool, then asks that pool for its flags.
M4 Pro, macOS 27.2. |
…on the Mac with a pair of Joy-Con 2s at the game's own 30 fps: its profile, note and table row (it needs PR seedds#1609 to boot, and PR seedds#1921, the first commit of this branch)
…: a recording's end, its links and its calls are in control memory, as in NVN, and what games record per mesh takes compact forms the queue expands at submit, so a typical mesh draw takes 88 bytes where the driver takes 132 and the host took 112 (PR #1609) Engines that size a model's prerecorded commands by measuring scratch recordings against a fixed resource budget ran out of it with the host's records: Super Mario Bros. Wonder's Nintendo Switch 2 Edition (NpcCommonerA 0x5424 of 0x5000) and Drag x Drive (5 s after boot), BufferInitialize with no pool, then a crash in nvnMemoryPoolGetFlags. PR #1609 shrank one-draw recordings to 4-byte tickets behind a profile key; this fixes the cause for every game instead. - Control memory: each recording's control list is 8-byte entries in the game's control memory (pieces of records, links to more control memory); the handle is the list. Command memory holds only records: no END, no JUMP, no CALL, nothing past what GetCommandMemoryUsed reported. GetControlMemoryUsed / Free count the entries; the game's out-of-control-memory callback is called (Cuphead refills from it), and the host lends blocks when it gives none. - CallCommands copies the called list's pieces into the caller, as the driver copies a command set's GPU FIFO entries: Donkey Kong Bananza resets its control arena twice per command arena, and while a call pointed at the called list the caller replayed overwritten entries (FATAL invalid NVN command size in about one 100 s run in six). CopyCommands copies the records. - Compact records (nvn_obj.h OPC_*): draws, viewports, stencil values, handle binds, uniform / storage / vertex buffer binds, vertex state, small states, barrier, primitive restart, scissor, with small arguments in the header's upper 24 bits. The queue's flatten (and nvn_rec_linear) expands each into the canonical records, so replay, capture and the replay walkers see no new op. - Sized against the driver's own commands: dm/tools/nvn_native_sizes.py finds the NVN driver in a game's sdk (names, implementations, the command buffer layout from its getters) and runs each call in Unicorn. NVN1 (Fire Emblem: Three Houses, Cuphead, Minecraft Dungeons II) and NVN2 (Ocarina of Time, Donkey Kong Bananza) agree; five calls stay above the driver's at the 8-byte header's floor (depth-stencil state, BindTexture kept for Luigi's Mansion 3's in-place patch, storage buffer, blend color, scissor). Table in docs/reference/memory.md. Checked: t_nvn 290/290 (51 calls recorded compact and canonical expand byte for byte; calls replay after the called list's control memory is overwritten, copies after both of the source's memories are; a list linked across control blocks), the gfx suite, lint. Minecraft Dungeons II on the Mac: the same picture in Squid Coast as before (its early stall at frame 120-140 happens on the old host too). Donkey Kong Bananza on the Mac (no shader libraries for this chain): three 100 s runs without a crash, command bytes per frame 127 -> 88 KB. Not run: the Wonder Edition and Drag x Drive (not on this Mac), iOS, Ocarina of Time (its pipeline preparation outlasts a run here). Co-authored-by: ishtiaq156 <25985759+ishtiaq156@users.noreply.github.com>
|
Thank you for the diagnosis and the measurements: they pointed at the cause. Landed on main as f02d047 with a different design, credited to you as co-author. Closing this PR in favour of it. Why not the tickets: a native indexed draw can't fit in 4 bytes, so the tickets made one kind of recording smaller than the driver's to absorb overhead from elsewhere, behind a profile key. The overhead was the host writing a recording's END (8 bytes), its chunk links and its calls into command memory. NVN keeps all three in control memory. What landed, for every game:
I couldn't run the Wonder Switch 2 Edition or Drag x Drive here. @FifteenPen, could you try Drag x Drive on main without |
…: a recording's end, its links and its calls are in control memory, as in NVN, and what games record per mesh takes compact forms the queue expands at submit, so a typical mesh draw takes 88 bytes where the driver takes 132 and the host took 112 (PR seedds#1609) Engines that size a model's prerecorded commands by measuring scratch recordings against a fixed resource budget ran out of it with the host's records: Super Mario Bros. Wonder's Nintendo Switch 2 Edition (NpcCommonerA 0x5424 of 0x5000) and Drag x Drive (5 s after boot), BufferInitialize with no pool, then a crash in nvnMemoryPoolGetFlags. PR seedds#1609 shrank one-draw recordings to 4-byte tickets behind a profile key; this fixes the cause for every game instead. - Control memory: each recording's control list is 8-byte entries in the game's control memory (pieces of records, links to more control memory); the handle is the list. Command memory holds only records: no END, no JUMP, no CALL, nothing past what GetCommandMemoryUsed reported. GetControlMemoryUsed / Free count the entries; the game's out-of-control-memory callback is called (Cuphead refills from it), and the host lends blocks when it gives none. - CallCommands copies the called list's pieces into the caller, as the driver copies a command set's GPU FIFO entries: Donkey Kong Bananza resets its control arena twice per command arena, and while a call pointed at the called list the caller replayed overwritten entries (FATAL invalid NVN command size in about one 100 s run in six). CopyCommands copies the records. - Compact records (nvn_obj.h OPC_*): draws, viewports, stencil values, handle binds, uniform / storage / vertex buffer binds, vertex state, small states, barrier, primitive restart, scissor, with small arguments in the header's upper 24 bits. The queue's flatten (and nvn_rec_linear) expands each into the canonical records, so replay, capture and the replay walkers see no new op. - Sized against the driver's own commands: dm/tools/nvn_native_sizes.py finds the NVN driver in a game's sdk (names, implementations, the command buffer layout from its getters) and runs each call in Unicorn. NVN1 (Fire Emblem: Three Houses, Cuphead, Minecraft Dungeons II) and NVN2 (Ocarina of Time, Donkey Kong Bananza) agree; five calls stay above the driver's at the 8-byte header's floor (depth-stencil state, BindTexture kept for Luigi's Mansion 3's in-place patch, storage buffer, blend color, scissor). Table in docs/reference/memory.md. Checked: t_nvn 290/290 (51 calls recorded compact and canonical expand byte for byte; calls replay after the called list's control memory is overwritten, copies after both of the source's memories are; a list linked across control blocks), the gfx suite, lint. Minecraft Dungeons II on the Mac: the same picture in Squid Coast as before (its early stall at frame 120-140 happens on the old host too). Donkey Kong Bananza on the Mac (no shader libraries for this chain): three 100 s runs without a crash, command bytes per frame 127 -> 88 KB. Not run: the Wonder Edition and Drag x Drive (not on this Mac), iOS, Ocarina of Time (its pipeline preparation outlasts a run here). Co-authored-by: ishtiaq156 <25985759+ishtiaq156@users.noreply.github.com> (cherry picked from commit f02d047)
Just saw this, apologies. I'll run Drag x Drive on main without nvn_draw_ctl and send a log when I wake up. |
Advance Wars 1+2: Re-Boot Camp aborted when the enemy used a CO power, about a minute into the reporter's save, since f02d047 (PR #1609): FATAL NVN command list ... record {op 8, size 504} ... 0 bytes into its piece of 16, in nvn_queue_submit. Cause, in the game's own code: a Unity rendering plugin in main (render-event handler main+0x1545800) rotates three command buffers, each with its own 1 MB block of one pool. For each render event it waits with nvnSyncWait(sync[i], infinite) on the fence it recorded at the end of buffer i's previous recording, re-adds block i from its start (AddCommandMemory at main+0x15458cc), records its work or nothing plus a FenceSync, and hands the handle to Unity, which calls it into the frame's list without submitting (main+0x96bb00). A frame normally has two events (traced on the Mac at the title). The reporter's list has four: B's 16-byte fence-only recording, C's work, A's fence, then B's work. The wait before B's second use is on a fence in the frame's own list, not submitted yet. The host answers at once, and the console must too: a wait that held there would stop the game. So B's block was recorded over while the list still called its first recording, and the 16-byte piece became the head of a 504-byte SET_RT. On the console the GPU runs whatever bytes are there. Before f02d047 a call was a CALL record to the handle, and the replay ran B's later recording to its END, twice, unnoticed. Since then the flatten checked each record against its piece and died. Fix: nvn_piece_record (hle_nvn.c), used by nq_flatten, nvn_rec_linear and CopyCommands. A piece runs what its command memory holds at submit, as the GPU runs a command set's bytes: the whole records that fit in it. The first record that does not fit, or a header that is no record, ends the piece with a [RED] line (the first 8, then every 1,000th) instead of aborting. Where the GPU would run part of such a record, the host runs none of it; here that drops the head of a SET_RT that B's own piece runs whole later in the list. Checked: t_nvn 314 checks, 0 failed, with three new ones in 5b''' (a called recording recorded over by a longer record: the piece is skipped, the caller's own record runs, the linear copy holds only that; by a record of the same size: it runs). test_docs, and lint_c.py tidy on hle_nvn.c and t_nvn.c (nothing above the baseline). Advance Wars on the Mac with the fixed host: boot, opening movie, title and main menu, no red line. Not run: the CO power frame itself (it needs the reporter's save), regress.py (only the path that aborted changes), iOS. New reference entry: docs/reference/nvn-objects.md, "A called recording recorded over before its caller is submitted"; symptom index and the game's note updated. Fixes #2437
Purpose
This is the generic, opt-in four-byte NVN command-ticket correction for BFRES model command-list budgets, separate from Edition profile #1563. It uses real command/control memory accounting, not a RESTBL increase or allocation bypass. This PR is Ready for review at
13d67c62, a normal two-parent merge of upstreameed7c429into the existing #1609 branch. The merge preserves the new asynchronous queue scheduler and four-byte tickets together.In the host recorder, a solitary direct indexed draw required 40 command bytes plus an eight-byte END, even where the native command stream can use a four-byte terminal command. A BFRES model then sizes guest model scratch from
nvnCommandBufferGetCommandMemoryUsed, and that additional host representation cost can exceed a fixed guest resource budget.General correction
nvn_draw_ctl=on(orDM_NVN_DRAW_CTL=1), an eligible single direct indexed draw plus END leaves a real four-byte guest command ticket. Its complete canonical draw/END occupies 48 real bytes of guest-provided control memory. Both command and control GetUsed counts remain honest; unsupported recordings take the existing fallback, not an allocation bypass.OP_CALLandJUMP, and copies the canonical DRAW_IDX+END into queue-owned memory before async capture/preindex/replay/commit. The queued copy outlives any subsequent guest regrant; unrelated default-off titles are unchanged.CopyCommandsmakes an independent canonical copy of a ticket draw.CallCommandsretains OP_CALL semantics, and the existing relocation/capture format is reused. Command-buffer reuse and more than one cached handle are exercised by focused headless tests.Measured original-RESTBL results
NpcCommonerA0x54240x4efc0x5000PlayerPeachElpWM0x400c0x3fd00x4000Those original-RESTBL measurements explain the previously observed null-pool crash and demonstrate that these model requests fit without the earlier diagnostic +16 KiB resource-table override. They do not demonstrate full-course gameplay or all possible model workloads.
Validation and integration
render_cond=on). These are synthetic tests, not a new game capture or current-head signed preview.dm/gfx/nvn/hle_nvn.cis resolved; GitHub reported MERGEABLE at published13d67c62. New-head CI is running. The independent upstream environment-registry omission forDM_NVN_BC_ALIASis not introduced by this ticket fix; separate CI baseline work covers it. Ready for review does not imply all checks or manual acceptance gates are complete.Limits and review gates
The device advertises a 512-byte minimum command-memory grant (device-info item
0x15). Our 32-byte and 128-byte synthetic grants are deliberately defensive, below-contract fixtures, not proof of native-valid command-memory behavior. Exhaustion of the 31-bit ticket generation space fails closed. Sustained multi-queue GPU overlap and direct host-lent handle origin/lifetime remain unproven.The preserved signed Edition preview uses shader chain
844bf1a5ea8f, while newer source expects incompatible3ee52af3ca18. I have not signed a current-head preview or regenerated shaders/PSOs. Original-RESTBL New Game progression, Sample Place Continue, complete course/Bellabel, audible sound/movies and physical rumble remain unverified. This PR is ready for source review; issue #1580 stays open for relevant CI/dependency review and manual validation. The real frame-2350 presented-image capture predates the new scheduler; the synthetic queue tests are not equivalent to a fresh full-game replay.