vk_exec_held_ready runs without sub_lock (vk_backend.c held_resume, submit_sync). It decided from the published copy
since 688b2506, but tested the wait with vk_exec_event_met, which compares at the copy point of the submission being
recorded (vk_exec_xfer.c ev_mem, ev_sync_seq). That point is set by whichever thread holds the lock, so a thread asking
while another one recorded tested "at sync" against that other submission's copies. Its question is whether held work
can go on in a submission of its own, which takes its copies later: vk_exec_event_met_now tests the value in memory now
or the signals replayed so far. Under the lock nothing changes: evo_resume and cont_ready keep the copy-point test, and
vk_exec_record asks right after its own copy point, where the two agree.
evo_pending and vk_exec_submit_seq are written under the lock and were read as plain values by vk_exec_held,
vk_exec_held_ready and the held present's since and PH_MAX checks; they are now stored and loaded as atomics
(vk_exec_submit_seq_now), so the compiler cannot keep a stale copy in an unlocked reader.
Not reproduced before the change. Checks that passed: pass_cc_check on every runtime file (a header changed), no new
-Wall -Wextra warnings in vk_exec.c, vk_exec_xfer.c and vk_backend.c, evo_peek_check, target_frames_check and
oq_hold_check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The RX 6000 reports of 0.9.1 and 0.9.2 ran the completion callbacks of submissions the GPU never finished before any call returned VK_ERROR_DEVICE_LOST, although the completion thread polled the device first. Every value a queue hand-over or a host signal gives the submissions' timeline or the executed timeline is now noted before the signal (vk_sync_hw.h); the completion thread and the wait after a submit end the run when a timeline reads above that note. The first occlusion query found unwritten after its fence logs its submission, the submissions' values, both timelines' readings and the GPU breadcrumbs, and the lost device's report starts with the same line, so the next report shows whether the driver gave a value above the note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A draw that finds every query slot still waiting for the completion thread goes uncounted, and the report after it said only what the other draws counted, at least 1: the game takes the object it tested for all but hidden for that frame. With the setting the report writes the larger of that and the last complete count reported into the same record. Off by default and read at start; the count of such reports is logged at the 1st, 10th, 100th and so on either way. tests/oq_hold_check.py checks the rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ClearBuffer or ReportValue is a fill the GPU makes where it stands in the stream, and guest memory has its bytes once the GPU got there. A counter report reaches guest memory as a CPU write after its submission's fence, and a render condition reads the record when it is replayed. So a report followed by a clear of the same record ended as the report (in one submission always, across two when the completion thread was behind the GPU), and a condition replayed after the clear read what memory held before the fill, or the 0 of the report the clear had overwritten.
A clear of at most 256 bytes into CPU-visible memory is now also applied at its place in the replay to the records it covers that a counter report went into: the covered bytes get the value in guest memory at once, reports replayed before the clear leave those bytes alone when their write comes, a condition reads them from memory, and a report replayed after the clear writes the whole record as before. The GPU's fill is recorded as before. Other small clears stay the GPU's fill alone: the GPU reads guest memory too, at once in an imported pool and through the next upload of the written page in a mirrored one, so a write ahead of it into a draw count or an atomic counter would reach passes that still want the old value. NX2NAVI_CLEAR_SMALL_CPU=0 turns it off.
Whether the game clears counter records is not known. The pass timing captures of 75 scripted runs show one buffer fill a frame and nothing else about it, and the frame dump does not list buffer fills. The periodic vk_usnap line now counts the small clears, the last one's size, value and address, how many covered a record with a report, the reports into bytes cleared before, and the report writes that left cleared bytes alone.
Checked with a build of the dev exe, the launcher tests, the device address table check, and a scratch link of vk_exec_xfer.c against stubs that replays reports and clears in both orders. Not run in the game.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The game waits each frame until the GPU has finished the frame before. In Hyrule Field with FSR 4 the last of that
work is one part of 4.7 ms, the temporal upscaler and the interface drawn after it, then 1.4 ms of write-backs, and
what the game reads of the frame is done before the upscaler runs. With NX2NAVI_TAIL_EARLY the replay records that
tail into a command buffer of its own, and when it hands the CPU nothing but timestamps, writes no pool memory but
inline uniform updates of mirrored blocks and reads no guest memory late, the write-backs go to the queue before it
with the executed timeline's signal. The game's waits end there, and the GPU runs the tail while the next frame is
prepared. The GPU's commands are the same; a tail that fails a test goes as before. Off by default.
RX 7900 XT, Ryzen 7 5800X3D, 3440x1440, FSR 4 Native AA, one batch, replay workers off: 50.7 against 42.5 fps
standing and 56.0 against 45.9 along the field route; with FSR 3.1, 51.8 against 47.8 and 54.9 against 52.3. With
three replay workers, FSR 4, standing: 63.5 against 49.4. The screenshots matched those without it standing, along
the route and through warps to five areas, with no black frame outside the loading screens of both.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
With NX2NAVI_REPLAY_PIPE=1 (off by default) the submitting thread hands a group's chunks to the workers, takes chunks
of it itself until none is left to take, and goes on with the ops after the group in a new command buffer after the
chunks' places in the part (vk_sync_append_later), also into the next group's front pass.
Up to four groups are handed out and not in their part yet; their chunks wait in one queue in the order of the stream.
The submitting thread waits for them only where the order needs it: for room, before a part goes to the GPU, before
copies and texture or buffer clears, where DLAA's run or the contact shadows' hook acts, and where a lookup would make
a texture entry or take back a retired one (the op goes again afterwards, as a chunk's does in turn).
What the chunks read stays as the stream had it while the submitting thread runs ahead: uniform snapshot drops count
from their place in the stream, a chunk reads only the inline uniform updates before its group, the HDR present's
source goes by stream position, the texture pool set syncs under the chunks' lock, each group keeps its own render
conditions, occlusion segments and closed query slots, and the queries after a group's last report go into the next
segment made after it. A group's first chunk runs its pass hooks after the group before began its pass; turns wait
for every earlier chunk, of earlier groups too. Locks the chunks take, the submitting thread takes while it is behind.
Draws keep the sampled-texture descriptors they resolved per draw context and reuse them without the texture lock
while their slot is unchanged; the workers waited for that lock at nearly every draw. Texture lookups that change
nothing no longer wait for their turn. The perf line says where the submitting thread waited, what the chunks took,
and which texture lookups waited; NX2NAVI_GPU_TIMELINE also shows the chunks and the waits.
Off by default because it does not pay yet: in Hyrule Field with 3 workers it shortens the replay (11.5 against 12.1
ms a frame) but not the frame (49.2 against 49.6 fps in the same batch). The GPU gets a group's commands only once its
last chunk is recorded, so the main thread's wait for the GPU after the frame's first submission grew by 0.8 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mesh draws ended a group and stayed on the submitting thread: 119 a frame in Hyrule Field at about 13 us each, and the
groups around them were cut short. They are recorded in the chunks now: the entries filled ahead for the indirect ones
are the draw context's, the ring index is taken atomically and the ring is made before the chunks record, each chunk
counts the ops that break a run of them on its own, and the lazy lookups of the mesh and shading-rate entry points store
their pointer before the flag that says it is there. With the mesh fallback, and while NX2NAVI_GPU_PASSES copies the
workgroup counts, they stay on the submitting thread.
A chunk's closed occlusion query slots go into a list of its own and into their segments once the group is done, in
chunk order, instead of under a lock per query. The per-object motion check decides alone per draw context (its
variant memo is per context) and takes its lock only to list a draw. The occlusion query ring has 16384 slots: the
chunks' blocks are sized to their draws, and with 4096 the ring came round to slots not reset yet.
The perf line splits the submitting thread's time with workers on: ops outside groups by kind, the front pass, its own
chunks and the wait for the others, and which ops ended the groups.
Hyrule Field, RX 7900 XT, 3440x1440, FSR 4 Native AA, same batch: standing 51.7 fps with 3 workers against 43.6 with
the workers off (replay 11.2 against 15.1 ms); along the field route 48.9 against 44.7. Screenshots standing and along
the route match the replay without workers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chunks of a parallel group took query slots from the ring as they recorded, so the slots of the group's chunks
interleaved. The completion thread reads and resets slots in runs that follow the ring, and with interleaved slots it
made four times the result calls and a reset per slot: the wait after a frame's first submission ended 3.5 ms after
the GPU had run it instead of 0.6 ms, and the replay time the workers saved went into that wait (44 fps with 3 workers
as without). The front pass now gives each chunk a block of slots, as many as its draws that count samples and render,
in the order of the stream; a segment's slots are taken in ring order. Standing in Hyrule Field, 3 workers: 49.8 fps
against 43.7 with the workers off.
NX2NAVI_GPU_TIMELINE=<frame> writes four frames from that present on to the log: each replay, each timed part's submit
and GPU run, the waits for the GPU and the workers' groups, so a run shows whether the GPU waits for the replay, a wait,
or the CPU behind one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Samples-passed resets and reports ended most groups in Hyrule Field (the game reports twice without a reset there, so
counting stays on): the front pass now replays them in the order of the stream, makes each occlusion segment and
queues its completion callback in that order, and each worker follows the counting from its chunk's first op and adds
the queries it closes to the segment the stream gives them. Per-object motion takes a lock instead of waiting for the
earlier chunks: its list's order does not change the picture. The locks the chunks share are taken only inside a
chunk, as outside groups the submitting thread alone replays; taken always, they cost the replay about 1 ms a frame.
The perf line counts the draws by what ended their group.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage 2 of NX2NAVI_REPLAY_WORKERS (default 0, the replay as before). A run of draws is cut into groups: a group ends
where the render targets change after its first draw or clear, and before an inline uniform update, a mesh draw and a
samples-passed counter op, which the submitting thread replays itself in the order of the stream. A group of 64 draws
or more is first walked by a front pass on the submitting thread: it applies the state and binds, settles the CPU side
of the timestamp reports and decides the render conditions in the order of the stream, and copies the replay state at
the first op of each chunk. The workers and the submitting thread then take the chunks in order and record each into a
command buffer of its own, which vk_sync_append puts into the part in the order of the stream.
The frame's pass hooks (contact shadows, DLSS) run in a group's first chunk, which the others start after. What still
depends on the order between chunks waits until every earlier chunk of the group is done: a texture lookup that would
make or take back an entry returns nothing and the chunk replays the op again in turn, and so do the contact shadows'
pass, per-object motion and the HDR pass's source. The tables the chunks share take locks (the shader table, the
descriptor lists, mirror use and GPU write notes, uniform snapshot drops, target frames, the DLAA camera votes and the
previous-frame motion notes); each chunk keeps its own occlusion query open, its own feedback memo and no pass marks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
That wait ends when the GPU has run the submission's own commands (wb_split = 1). The completion thread was then still
at the last frame's submissions, so the occlusion counts, counter reports and event values the game reads next reached
its memory after it had gone on. A player's capture in Hyrule Field shows the result: trees, ground and walls lose
their direct light for single frames about every 36 frames, 57 of 1,800 frames, and with the fence's wait
(wb_split = 0) the same place showed none.
The fence's wait also covers the rest of what the completion thread does for those submissions, 0.23 to 0.40 ms a
frame of page stamps the game does not read. So those results now have a queue of their own
(vk_completion_add_result), and the waiter runs the ones due for its submission and the ones before it as soon as
the GPU's signal is there; the completion thread runs them the same way, ahead of its other callbacks, one runner at
a time and in the order they were queued. wb_split = 3 is the bare wait as it was, to measure against.
Checked: it builds, the launcher tests and the address table check pass, and the player's hand test of this wait at
that place showed no such frame. Not measured: no scripted run of it was made (the one scripted run with the bare
wait in that field shows 1,170 occlusion counts a frame written late and no dark frame, so the script does not
reproduce the fault), and its cost against the bare wait is not known.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
For a day that wait ended on a second timeline, when the GPU had run the submission's own commands (wb_split = 1,
then the default), on the thought that it only left out write-backs nothing needed yet. It left out the completion
thread's side effects for the submissions before it as well, and the game reads some of those next: the occlusion
counts, counter reports and event values, and the stamps the texture checks read. A frame's first submission is small
and runs right behind the last frame's large one, so the game went on while that one's callbacks were still running.
Measured in Hyrule Field with the camera turning (RTX 5090, lazy write-back on): 2,267 of 2,320 such waits returned
before those side effects had run; they were through 0.45 to 0.8 ms later on average, 12.6 ms at most, and in that
time 770 to 1,015 occlusion counts, 21 to 28 counter reports and 17 event values a frame reached guest memory. The
submission the game waits for wrote nothing back in any of 14 runs, so its own write-backs were never the matter.
A player saw a patch of grass near the camera change from frame to frame there and less of it with the fence's wait.
No scripted run showed such a frame with either wait, so the link to what the player saw is not shown.
The fence's wait takes 0.65 to 0.7 ms more of a frame there, about 6 % at 85 frames a second (single runs beside
other work). wb_split = 1 still gives the other wait, and 2 forces it with shadow storage.
NX2NAVI_TRACE=perf now counts what a wait on that timeline leaves behind and the completion thread's time by callback.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
(cherry picked from commit 4d108b706e72c001ddbe20608f06469885df584b)
A lost device used to end the process from whichever submit or wait noticed it first, while the present only logged it
and went on and the other threads kept calling the driver. The completion thread could take the submissions' timeline
as reached and run the callbacks of submissions that never finished.
vk_device_lost_check now takes the result of every submit, present, acquire and wait. The first thread to see the loss
sets a flag, writes the report and ends the process as a fatal error does (the log written, a save commit under way
finished), with an exit code of its own; later threads stop in that call, and a watchdog ends the process after ten
seconds should the first thread hang. The replay, its submit, the uploads, the present and the lazy fetch read the flag
and return without touching the device. The completion thread asks the device with a call of its own before a new
value counts and runs no callback once the flag is set.
The executable's --probe-devlost and tests/device_lost_check.py check this without a lost device, and
device_lost_at in nx2navi.ini ends a real run the same way.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Both are about the time between two frames. Ocarina waits for the previous frame's fence before it lets the next frame's GPU work start: its main thread signals an event from the CPU, and the main queue's commands begin with a wait for it.
The completion thread ran a submission's callbacks before it let the submission's waiters go, and the occlusion counts were among them: one vkGetQueryPoolResults and one vkResetQueryPool per reported query, about a hundred queries a frame in Hyrule Field. On an RX 7900 XT (26.10.1) each call goes into the kernel, 3.9 ms of every frame, so the fence was reached 4 to 6 ms after the GPU had finished with every game thread waiting. Slots come from the ring in order, so one call now reads up to 256 slots from the one asked for, the available results are kept for the segments that follow, and the slots taken are reset in runs (vk_exec_xfer.c oq_take, NX2NAVI_OQ_BATCH). Standing in Hyrule Field at 3440x1440 with FSR: 52.7 fps against 44.6, the longest frame of the measured minute 46 ms against 55; the completion thread spends 0.3 ms a frame in the callback.
When the CPU's signal came a millisecond or two after the main queue's submit call, the submission took its copies, stopped at the wait and was held; the render thread polled the event, presented, and the present replayed the frame under a value the game's fence does not stand for. The next frame then skipped its wait for the GPU and was replayed inside its submit call, and frames of 23 and 15 ms took turns. A submission that begins with a wait for an event the CPU has signalled before now waits for the signal in the submit call, before its copies are taken, 10 ms at most (vk_exec.c vk_exec_gate_wait, NX2NAVI_GATE_WAIT_MS): 54.5 fps and a longest frame of 33 ms in the same place, 58.6 fps with the recorder thread on. With a camera sweep the pictures went out 21.0 ms apart on average with a 99th percentile of 55 ms, against 25.4 and 64. A village went from 48.6 to 53.7 fps and a graveyard from 56.5 to 64.2. 900 consecutive frames of a still scene show no dark or changed frame, with the recorder thread and without.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Each frame the game sets one of two events to 0 from the CPU, submits its worker queues and the main queue, whose first command waits for that event to be 1, and signals 1 with nvnEventSignal once it has written what the frame's commands read. The recording of a submission takes the VRAM mirror sync and the uniform snapshot at its start and tested the wait a moment later, so a signal that arrived in between passed the wait and the frame was drawn from copies taken before the signal: single frames with the whole scene's shadowing missing (6 to 7 of 255 brighter, the sky and the interface unchanged) or a black scene, more of them the busier the CPU.
Every nvnEventSignal is now numbered and keeps the value it replaced, the recording notes the number before it takes its copies, and a signal with a later number does not count for that submission. The submission stops at the wait like any wait reached before its signal and goes on at once in a submission of its own, whose copies are taken after the signal. NX2NAVI_EVENT_AT_SYNC=0 gives the earlier test. NX2NAVI_EVO_TRACE=1 logs the order in which every frame's submissions, waits and signals were replayed, which is how the frames were found.
Measured with the flight recorder's thumbnails, 1,800 presented frames a run, standing in Hyrule Field by day with the lazy write-back on, DLSS Performance, frame generation 4x and HDR: the build before this change showed 34 single-frame spikes with eight busy threads beside the game, 20 with four and 8 with six, most of them in frames whose signal came during the replay; with this change two runs with eight busy threads showed 0 and 0, with 667 and 724 such frames. A session of the user's with the lazy write-back off ran 25,200 frames without a spike in its three automatic dumps while the change stopped 4,744 wait tests.
Not measured: runs of this same build with the switch off (the two tried failed for other reasons, a device loss during the boot beside another game and a load with cold caches), any run with the lazy write-back off under load, the frame rate and GPU time against the switch off, and the differential tests, which this change does not touch. The flicker with the lazy write-back off that was reported has not been reproduced in a capture; its mechanism is the same signal and occurs there too.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A session with per-pass GPU timestamps showed the game's own render passes and compute dispatches at 1.6 to 2.3 ms of GPU time a frame while the perf line's GPU time was 7.7 to 13.5 ms: most of the frame's GPU time is in commands the replay records between the marks, and nothing said which.
The timed frames now also get a timestamp at the start and the end of each part of a submission, and every stretch between two marks (or a mark and the part's edge) is a gap. The replay names the kind of commands it records in a gap: buffer and texture copies, fills and clears, the temporal upscaler, write-back merges and copies, depth twin copies, the texture pool marks, the mesh draw counts and block-compressed target copies, each with a size where it has one. The report adds the gaps' time per frame summed by the kinds noted in them (a gap nobody noted holds barriers and state only), the parts' own time for comparison with the perf line, and each kind's count and size per frame. Nothing runs outside the timed frames.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
From the local fixes package (fix 11), measured in Kokiri Forest at 4K on an RTX 4090 over PCIe 3.0: 40.5 to 62.6 fps
native and 45.6 to 74.9 fps with DLSS Quality, the mirror copies from 6.5 to 0.3 ms of GPU time a frame.
- Pages a VRAM mirror wrote back into guest memory are not copied up into it again: the mirror sync reads page stamps
of their own that a mirror's write-back leaves alone (vk_gpu_written_back, decided at record time by
vk_mem_mirrored). This replaces the lookup table NX2NAVI_WB_NO_REUP had, keeps its name, and is now on by default.
- The game's flushes of mirrored memory are not copied where the write watch covers the region, as every page the game
wrote there is stamped already (47-57 MiB a frame); NX2NAVI_FLUSH_UPLOAD=1 copies them as before.
- Written storage ranges up to NX2NAVI_WB_MERGE_MAX (now 32 MiB) are merged in 4 MiB pieces, so a range is no longer
bound to what one dispatch covers; the snapshot pool keeps its budget-scaled size with 128 slots.
- A submission's first part goes to the GPU at the first pass boundary while the GPU is idle (split_idle), long passes
are split too (split_in_pass), and the part's time limit reads the clock less often.
- A render target that needs an image of its own takes the image of a released one with the same parameters, released
two or more frames before (tex_reuse); memory requirements are kept per parameter set; a released entry another
descriptor's entry replaced at its address is kept and taken back.
- The memory check reads a page's write-watch bit and stamp under the lock that keeps them together.
- The launcher reads the gamepad only while its own window is in front, and does not start a second copy of the game.
- make_game.py says why a Python package did not load, not only that it is missing.
The fix's per-pass GPU timing is left out: NX2NAVI_GPU_PASSES does the same in main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lock_time (NX2NAVI_LOCK_TIME) makes the replay threads time each lock they have to wait for, kept per asking site (platform.h plat_lock_timing, plat_mutex_lock_at and plat_rmutex_lock_at carry the site), and the frame-time summary lists the sites with the most wait, the recorder's full-ring and drain waits, and the texture lock's holds over 1 ms with the code that took it. replay_time now also keeps each frame's replay ops: a frame over 25 ms charged to the replay says how many draws, binds and transfers it replayed, their time, its slowest op, and how much of the replay's wall time the thread was on the CPU. NX2NAVI_PROFILE_REPLAY=1 samples only the replay thread without call chains, and the profiler now follows the thread that replays instead of the one that started the back end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Large compute shaders that no cache holds are built on one background thread after the boot prebuild, smallest first, so their first dispatch no longer compiles them on the replay thread. NX2NAVI_BIG_BUILD_MAX_MB stops that builder once the process has grown by that much: on a 31 GB machine one of these shaders took the process up by 12.8 GB and the game stuttered while Windows paged. The builder takes the queue under its lock and copies each entry out before compiling it, so a shader queued meanwhile cannot free the arrays under it. The same change reports what pipeline-cache.bin gives back, and starts over when the file grows past NX2NAVI_PIPELINE_CACHE_MAX_MB.
A tensor-core program is now tested before its shader is asked for, in both dispatch paths. It runs the stand-in while NX2NAVI_NATIVE_TENSOR is off, and translating it anyway recorded four AI-upscaler shaders of 0.87 to 4.4 million SPIR-V words that this game never dispatches natively. Process memory went from 29.3 GB to 24 GB and the multi-gigabyte builds during play are gone.
The frame-time summary says which part a slow frame went to, and with NX2NAVI_REPLAY_TIME how the replay splits between draws, binds and transfers; NX2NAVI_SVC_SLOW_MS logs the kernel services that block, with the thread they blocked on. prof_report.py reads the map this build writes and adds the image base to a runtime Rip, so a profile resolves to function names instead of question marks.
build_xlate.py applies the patches with GIT_CEILING_DIRECTORIES set to the build folder's parent. Inside a checkout git read 0021-prmt-byte-permute.patch's paths from the repository's top, skipped every file of it and still exited 0, so PRMT was missing from locally built translators and every byte permute was a no-op. A Skipped patch line now fails the build. The note in AmpereLowering.cs that said the translator emits nothing for PRMT is corrected.
Ocarina runs compute work on a second queue that trades results with the main one through events: the worker's command buffers wait (WaitEvent) for signals the main queue gives in its submission a few submits later, and the main queue waits for the worker's signals in turn. Replayed one submission after the other, the worker's steps ran before the work they wait for and read the previous frame's results, and the characters came out much too dark at any time of day (Kokiri Forest: about 13 waits a frame, 7 of them reached before their signal).
A top-level wait whose condition does not hold in replay order now stops its submission: the rest is held, with private copies of its recordings and the queue's state, and the queue's later submissions line up behind it. After each replayed signal the held work whose wait now holds goes on inside the command buffer being recorded, between full barriers, with its own state swapped in. Work still held after 36 submissions goes on regardless, as before (counted in the NX2NAVI_TRACE=perf line). The queue reaches the back end as gfx_submit_queue. Only with the replay on the submitting thread; NX2NAVI_EVENT_ORDER=0 turns it off.
Tested by play in adult Hyrule Field with the tree fix of the previous commit: Link lit normally at dusk and at noon, trees steady, lighting otherwise unchanged. The white specks in the grass remain (they predate both changes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The culling passes count each frame's visible foliage with atomics through the device address table into small records (an indirect draw's count, then 1, 1). Stores through the table already reached both the VRAM mirror and its guest copy, but atomics reached the mirror only, so guest memory held the CPU's template of zeros. The CPU writes other words of the same pages every frame, and the sync of such a page copied those zeros over the counts: in Hyrule Field the trees flickered, went missing or stretched into columns reaching the sky. NX2NAVI_MEMORY=verify found 5 to 10 such pages a minute, all in ten pools of 20 and 44 KiB.
Translator patch 0036 repeats every table atomic on the twin (a compare-exchange with the same operands), leaving its result unused so the driver need not wait on guest memory. A buffer fill into a mirror now fills its guest copy at once (vk_mem_guest_twin), so a counter the GPU resets that way counts on from the same value in both; that mismatch is why table atomics stayed out of the twin before. The memory check's mismatch lines now also give the pool's last written binding, its last use, the CPU's write stamp for the page and the first bytes of both copies.
With the change the trees stayed steady at the spot where they flickered, all 37,848 shaders translated without a failure, and the GPU time per frame stayed at about 9 ms (75 fps uncapped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Small ranges the game invalidates and reads back (the exposure meter) are copied from guest memory into the mirror at every submission that uses their pool, so a CPU write made between recording and the GPU's run is not lost; NX2NAVI_READBACK_SYNC=0 turns it off.
While the boot builds the recorded pipelines a marker file exists next to the pipeline cache. A start that finds it left over records the GPU and driver in pipeline-prewarm-skip.txt, and starts on them build those pipelines at their first draw instead.
CommandBufferWaitEvent is now recorded as OP_WAIT_EVENT. The replay waits for nothing but counts the waits it reaches before their signal, and how many submissions later that signal comes, in the NX2NAVI_TRACE=gfx frame line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each file that contains copied code names its copyright holder and license and points at THIRD_PARTY_NOTICES.md; zlib code keeps its full notice and the mark that it was altered. Comments and probe text only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments say what the code does and why, docs/maxwell.md keeps the Maxwell facts, and fetch_refs.sh clones only the checkout the build reads. Code is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A texture slot whose content the CPU writes now follows another image at its address only when that image differs in
swizzle alone or holds GPU-written content; Ocarina reuses freed glyph atlas memory for captions of another width, and
following the older image drew the Basic Controls screen's "Target" with the wrong row length. Mesh-shader pipelines
are built on the workers and recorded in pipeline-keys.bin (version 4 with the task stage; version 3 files are
converted) instead of stopping the frame for 0.26-0.51 s. The ASTC decoder reuses one scratch buffer per upload command
buffer, and CPU-provided textures up to 32 MiB are sub-allocated unless the driver requires dedicated memory, which
takes the scene-load frame from about 1.25 s to 0.7 s. The feedback scan looks only at bound texture slots instead of
the bindless pool arrays, per-draw knob lookups are cached and the texture address buckets grow to 65536, so Kokiri
Forest's replay falls from about 16 to 7-9 ms a frame and the forest holds 60 fps. A float clear of zero on an integer
image now clears instead of being skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The texture pool descriptors are now a ring of sets, one per submission, brought up to date from a change log, and the first submission of each frame waits for its GPU work before the game goes on: the GPU ran late and part of a frame read data of the next one, and distant characters drew NaN that bloom spread over the frame. The window acquire also waits for the image's previous frame. Function locals start at zero unless DLAA is on, which removes the per-warp light tiles on the Great Fairy Fountain floor and Link's dark, speckled look. Texture slots follow only live entries of their own target, so the volumetric fog gets its 3D volume. The pool refresh walks only the slots that changed.
The translator implements PRMT (patch 0021), which it only logged before; the button prompts render again. HFMA2.RELU no longer depends on it, HMMA.F16 rounds once and HMMA.F32 is lowered, checked against GPU vectors with a new Maxwell warp emulator.
The frame dump gains a re-arming white-frame trigger, a trigger on a depth target's first pass, NaN pixels in magenta, image handles and view targets, and depth copies of sampled depth textures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The NVN2 front end and the SM86 to Maxwell shader lowering (tools/xlate/AmpereLowering.cs, mesh and task stages,
bindless texture pools, BRX jump tables, the NVN2 compute c0 layout) with four translator patches (0017-0020) bring the
opening of the game to 58-60 fps on the Vulkan renderer.
This round fixes the haze: SM86 IPA is lowered as a plain Maxwell IPA.PASS (texture coordinates were corrected for
perspective twice); LDSM, TXD, both F2IP byte forms, HMMA, MOVM and HFMA2.RELU are lowered and spill mode has eight
uniform register groups; conditional rendering keeps the newest counter report per address and treats a report as
pending until its value is in guest memory, so occlusion tests and the depth clears around the depth copy behave; and
the upscaler stand-in decodes the post pass's sRGB picture before writing the linear target. Tensor-core programs keep
the stand-in unless NX2NAVI_NATIVE_TENSOR=1, because their lowering does not match the hardware yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>