Skip to content

OoT: mark native compute stores only as far as they reach - #4

Open
xTektor wants to merge 161 commits into
nx2from
cursor/oot-kokiri-playable-bf53
Open

xTektor wants to merge 161 commits into
nx2from
cursor/oot-kokiri-playable-bf53

Conversation

@xTektor

@xTektor xTektor commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Changes

Twelve commits. Commits 1–4 are unchanged and 4 is now measured. Commits 5–12 are new and unmeasured. No FPS claims for them.

1. Native compute write marks (a47c72f3b0)

A Switch 2 compute storage pointer has no size, so a written binding was marked GPU-modified for up to 8 MiB. Ocarina skinning writes tens of kilobytes; the rest of that range is the CPU's own data. Fences copied it back, and the CPU's later writes waited.

The recompiler now proves how far each store reaches. A dispatch marks only those bytes. An unproven store still marks the whole binding. Reads are unchanged.

Legion result (MSVC 5fb53b44, Vulkan, docked, 1x, GPU accuracy Low, no DLSS, no Dynamic FPS): buffer downloads are 0 KiB in every gameplay window. Outdoor Kokiri was still about 10–16 fps. The download path is no longer the limiter.

2. Occlusion-report DMA reads (88f1b3d9ae)

The front was draining the whole back thread on every macro DMA read, about 27,000–30,000 reads a second. Those are occlusion reports. A read of a landed report, on a page shaders and copies have not written, now comes from guest memory. Anything else still drains.

Legion result (MSVC 88f1b3d9, same settings, two outdoor sessions including a warm cache): macro DMA read wait fell from about 270–373 ms/s to about 20–100 ms/s. Warm village under trees is typically 20–28 fps, where the previous build was 10–14. Walking is often still 12–14. Camera turns are typically 20–30. Overall outdoor is about 12–25, overlay average 15. Stalls to 0 fps remain. Buffer downloads stayed 0. The canopy haze is still there. Not playable 30.

Split drain stayed high (session 2 often 250–580 ms/s) because acquire waits did not move (160–490 ms/s, mean about 280).

3. CPU semaphore acquires (3863d03ef9)

zortos293#337 is not in this branch. CounterReport still treats a fence-flagged report as synchronized at GPU accuracy Low with Default fences, and the fence manager can still skip an empty fence. The acquire cost in the 88f1b3d9 profile is not the host-GPU wait zortos293#874 removes from zortos293#337. It is DrainBack on every acquire check, about 1,600 blocking drains a second, including polls of a semaphore the CPU releases.

zortos293#874 therefore does not apply here. zortos293#805 and zortos293#717 were not ported.

A read now waits only until a queued GPU release of that word has been executed. When none is queued, the host write has landed and the buffer cache is clean, the word is taken from guest memory and the back is not drained. A value that already matches is not trusted while a release of that word is still queued, which is the stale >= case from zortos293#231. A dirty buffer cache or an unfinished host query still drains and flushes.

Query ordering is unchanged, so this does not reintroduce the white flashes zortos293#337 fixed on trees that have that commit. A bug here would be a channel running past an acquire whose GPU release had not run.

Legion result (user play around Kokiri, same settings, window title 3863d03ef9): acquire site wait mean 93.6 ms/s versus 278.6 on 88f1b3d9. About 2,214 acquires/s, of which about 6.5% waited. Split drain mean 214.9 ms/s versus 412. Outdoor overlay on the verified frames is typically 19–24 fps (one 10 fps village sample, one 0 fps stall). Not stable 30. Canopy haze remains. Buffer downloads stayed 0.

What did not fall: writer wait mean 370 ms/s, list writers 276 ms/s, list GPU wait 199 ms/s, fence release 115 ms/s.

4. Command-list copy no longer blocks the front (ec8e93f750)

Those writer and list-GPU waits are the front standing in WaitSubmittedTick until the GPU finishes the copy of a command list the channel is about to read, after WaitForWriters. Upstream zortos293#752 measured the same pattern in Kokiri Forest: about 2,400 such waits a second, about a quarter of the time, with the back idle and more work already queued. This is that change, not the rest of zortos293#805.

A copy that is not finished suspends that channel, the same way a semaphore acquire does. The scheduler runs the other channels and the queued work. The channel parses the entry again once the copy's tick is done. The list is still read only after the GPU wrote it. Resuming skips the writer wait, because that wait already ran and waiting again would also wait for other channels' work queued during the suspension. If the step returns before that read, the skip is cleared.

No fps number for this commit when it was pushed.

Legion result for ec8e93f750 (outdoor Kokiri, same settings): 12–26 fps. Profile per second: front writer wait ~378 ms/s, list writers ~284, split drain ~232, pending sleep ~193, resume pending ~103, acquire ~93, macro DMA read ~102, buffer downloads 0, host_cpu_busy ~336. The old list GPU wait counter is gone, since the front no longer stands in the copy wait. Writer wait and list writers did not fall, which led to commit 5.

5. Command entries between methods skip the compute-writer wait (62a5a42b3)

Finishes the unpushed slice of the Cursor agent. The front ran WaitForWriters at every command-list entry. Now it does that only when the entry starts a new method (method_count == 0 skips it) or when a DMA copy writer is still pending (CommandEntryWaitsForWriters). The pending copy writer is tracked by NoteCopyWriter. A command-list read waits only for writers up to the last sync (WaitForSyncedWriters, a new list copy writers drain site). The GPU thread also publishes before the front sleeps on an empty queue. Credit: jpkto, and zortos293#805 (d4e8d85d4d4f). The -mtls-dialect=gnu2 change was left out. Unmeasured.

6. Transcode cache and dead-data downloads (6dad7ae89)

From javocsoft#17 (edab3cc934). Transcoded textures are kept in host memory, and the GC skips downloads of GPU data nothing will read again. The log reports with dead data deletes and the transcode cache stats. Unmeasured.

7. Back macros, opt-in (82713612c)

From Frizti#24 by mkooner (2f82fd2d5755, a port of jpkto a442c38fe6). Macros that read memory run on the split's back thread, and AnalyzeEffects proves which registers they write. Off unless EDEN_NX2_BACK_MACROS=1. Unmeasured.

8. OoT zstd on the host (a6fe18b57)

From zortos293#869 by Xiphias (4e3142e4f912). Ocarina of Time's zstd decompression, streamed blocks included, runs on the host via an SVC hook. It is gated on the main build ID E0F3CCFA… (matches the user's log) and the expected original code. Otherwise it does nothing. EDEN_NX2_OOT_HOST_ZSTD=0 turns it off. Unmeasured.

9. UsageTracker full pages (322e4ddfd)

From Spikol02/Nyamu#15 by Spiks. One bit per page records pages already full, so they are skipped 64 at a time. EDEN_NX2_USAGE_TRACKER_SKIP_FULL=0 turns it off. Unmeasured.

10. Native graphics SSBO sizing (f76899f23)

From Spikol02/Nyamu#21, itself from Frizti/Fritchii 82a57c333175. The cost was found in zortos293#101. A native graphics storage pointer takes the next word as its size instead of binding up to 8 MiB. This was not already in the tree. EDEN_NX2_GRAPHICS_SSBO_BARE=1 restores the old binding. Unmeasured.

11. Cheaper kernel locks and memory traps (0d57a3921)

The kernel-lock commit of javocsoft#16 (e0ae6b08b99f), from zortos293#717 by Stephen Jayakar. It covers KSpinLock, the KAddressArbiter spin, PhysicalCore idle, the KThread context guard, and GPU memory trap caches. Occlusion Off and the rest of zortos293#16 are not taken. Unmeasured.

12. FFMA/HFMA2 FMZ with SAT (c684f4af8), visual

From zortos293#825 by pvns1108 (fixes zortos293#745). zortos293#825 was picked over zortos293#811. FMZ+SAT no longer drops the addend, and the F16 FMZ select is fixed. This is the candidate for the canopy haze. Not checked in game. EDEN_NX2_OOT_VISUAL_WORKAROUND is not in this tree. It is a temporary opt-in per-shader edit in selevet-eglantine and was not ported. Unmeasured.

New switches

  • EDEN_NX2_BACK_MACROS=1: enables commit 7. Off by default.
  • EDEN_NX2_OOT_HOST_ZSTD=0: disables commit 8.
  • EDEN_NX2_USAGE_TRACKER_SKIP_FULL=0: disables commit 9.
  • EDEN_NX2_GRAPHICS_SSBO_BARE=1: reverts commit 10.

What this does not claim

Commits 5–12 are unmeasured. For commit 5, watch writer wait (~378 ms/s) and list writers (~284 ms/s). A wrong resume would show up as a command list read before its copy finished: garbage methods, a channel stuck on a release, or popped geometry. Not a claim of sustained 30.

Testing

  • storage_write_extent: 13 tests, 61 assertions, passed.
  • async_report_settled: an issued slot blocks until it completes. Passed.
  • semaphore_acquire: a CPU word is read from memory; a queued release is waited for even when the old word would match; a dirty cache or unfinished query drains; the newer of two releases is the one waited for. 4 tests, 8 assertions, passed.
  • list_read_resume: a resumed read skips the writer wait once; clearing the flag before the read does not skip a later wait. 2 tests, 4 assertions, passed.
  • dma_pusher.cpp, vk_rasterizer.cpp, and the Vulkan BufferCache instantiation build with -Werror.
  • command_entry_wait (new), async_report_settled, semaphore_acquire, list_read_resume: passed.
  • transcode_cache, range_cover (new): passed.
  • turing_macro_effects (new): passed, 6,785 assertions.
  • oot_zstd_hook.cpp, svc.cpp, nso.cpp: compile. No upstream test.
  • usage_tracker: 7 cases, 279,715 assertions, passed with and without EDEN_NX2_USAGE_TRACKER_SKIP_FULL=0.
  • kernel_locks (new): 2 cases, passed. Kernel TUs build.
  • maxwell_fmz (new): 4 cases, passed. 3 of them fail against the old translation.
  • Tests were linked standalone against Catch2 and run on Linux with GCC 14; the Windows package is built by nup-romfs-windows.
  • Not run: the game.

Legion check

Same settings as the 3863d03ef9 user-play run. Outdoor Kokiri, trees and walking, not the title screen.

  1. EDEN_NX2_GPU_PROFILE=1. Compare against ec8e93f750: writer wait ~378, list writers ~284, split ~232, acquire ~93, DMA ~102 ms/s, downloads 0.
  2. Overlay fps on village, rocks, and the stream. Still no playable-30 claim until that measurement.
  3. Watch for a list read that ran early: unbound subchannels, a channel that never resumes, or geometry popping in.
  4. Visual: canopy haze A/B (commit 12), and missing geometry or vegetation (commit 10; try EDEN_NX2_GRAPHICS_SSBO_BARE=1).
Open in Web Open in Cursor 

zortos293 and others added 30 commits November 6, 2026 00:09
* frame dump: port buffer, sampler and launch records from fix/oot-fog; capture_frame buffers option

Ports the frame-dump part of 1ad14be (fix/oot-fog): the uniform and storage
buffers of every draw and dispatch (kind: buffer, buffer_<hash>.bin.zst),
the host sampler state and raw TSC words of sampled textures, and dispatch
group counts (kind: launch). Its F2I constant-buffer abs/neg shader fix is
not part of this port.

EDEN_NX2_FRAME_DUMP_SAMPLED still records all of it. At runtime,
capture_frame takes buffers separately from sampled, so a constant-buffer
diff does not pay for texture readbacks.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* debug server: read_memory, search_memory and read_gpu_memory

Read-only memory tools for the debug server, capped at 64 KiB per read.

read_memory and search_memory run on the server thread. They resolve each page
with Memory::GetPointerSilent, the lock-free lookup guest cores use: it does
not commit the sparse page table, log, or download from the GPU. Every mapped
entry points into the DeviceMemory backing, which stays mapped for the whole
boot. search_memory scans the heap (or the whole address space), stops at 256
hits or its time budget, and returns where to resume.

read_gpu_memory is queued to the rasterizer thread like a capture. At frame
end it reads the first address space that maps the whole range (the bound
channel's first) with IsFullyMappedRange and ReadBlockUnsafe, without
flushing GPU caches.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* debug server: input and input_sequence

Inject buttons and sticks on player 1 (or the handheld controller) for an
exact number of presented frames, so agents can replay identical motion for
A/B tests.

EmulatedController::SetInputInjector installs a function that GetNpadButtons
and GetSticks consult while it is set: injected buttons are ORed in, an
injected stick replaces the real one and its direction bits. No mapping or
setting changes. The script starts at the next present and picks its step
from g_nx2_present_count on every read; it stops by itself after the last
step, and the handler removes it on completion, timeout, shutdown or client
disconnect (a ClientGone probe the transport now gives every handler).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* buffer_cache: default native CPU reads to trap-and-flush

Avoid permanently preflushing GPU-written pages after native CPU reads. Retain non-native preflushing and EDEN_NX2_PREFLUSH=1 as the fallback; EDEN_NX2_PREFLUSH=0 forces trap-and-flush.

Adapted from 0360f30 and ef3a246 in the OoT integration tree. Unlike that tree, keep the default preflush behavior for non-Ampere titles.

* buffer_cache: download every read-back range at a guest read that must flush

Ocarina of Time's guest CPU reads the output of ~30 compute storage buffers
each frame, page by page, in a few bursts. With trap-and-flush reads
(EDEN_NX2_PREFLUSH=0 for Switch 2 titles) each page read after a GPU write
flushed only its own page: ~1180 reads/s, each with a full Finish
(~170 us), 46 per frame, stalling the reading core and the GPU thread.

Remember the ranges the guest CPU read after GPU writes. When a read must
flush, download the GPU writes in all of them in the same Finish, so the
reads that follow in the burst find nothing to flush. A GPU write after it
marks its range modified again, and the next read flushes as before.
DownloadMemory also clamps each download to its buffer: a range may now
span several buffers.

Kokiri Forest (i7-12700K, RX 7800 XT, owner settings, seconds 44-84):
Finishes 1184 -> 70/s; mean 25.97/25.16 -> 30.75/30.73 FPS (median frame
38.6 -> 32.4 ms, p95 41.8 -> 34.6 ms). Grass, canopy and haze unchanged.

(cherry picked from commit 23d3388)

* buffer_cache: cache the preflush environment override

* nx2: sampling profiler in every frontend and the debug server; fence download trace

The in-process sampler moves to common and starts at boot (EDEN_NX2_SAMPLE) for the Qt
frontend too; the debug server's sample method runs it on demand. EDEN_NX2_DOWNLOAD_TRACE
logs which GPU writes queue fence downloads, which regions are copied back, and which CPU
reads mark ranges preflushable.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* nx2_sampler: format the report with fmt

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* nx2_profile: per-method and per-macro DMA read counts with the zones

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* debug_mcp: list the sample tool in the adapter test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* nx2_debug_tools: close SampleThreads after the merge

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* memory_manager: answer MaxContinuousRange from the run through the address's 64 KiB page

The per-address cache missed in Ocarina (a new address per object): walking up to 8 MiB of
pages took ~11% of the GPU back thread. EDEN_NX2_RUN_CACHE=0 restores the per-address cache
and =2 checks every answer against a walk (experiment switch, not for nx2).

nx2_profile: time zones with the unfenced time stamp counter; the fenced wall clock reads cost
~10% of the back thread.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* memory_manager: walk a page's run only as far as asked; count run cache hits and page table changes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* experiments: EDEN_NX2_DMA_NO_DRAIN; run cache off by default; sampler reports hot instructions

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* memory_manager: key the page runs by 4 KiB page (Ocarina shares big pages between allocations)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: EDEN_NX2_PERF_OPT master switch and EDEN_NX2_PERF_VERIFY for the back-thread optimizations

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* vulkan, nx2_gsv: port zortos293#161's submission batching and Ocarina frame-ahead present behind EDEN_NX2_PERF_OPT

From 999sian's PR zortos293#161 (3985ac2, 7422d90): a 4096-draw native submission
budget outside MoltenVK, FragmentBarrier deferred to the next draw that reads
what the pass wrote, a 5 ms DeferFlush interval on the split back with command
list copies submitted at once, and Ocarina presenting a frame ahead
(EDEN_NX2_PERF_FRAMES_AHEAD=0 turns only that off).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* buffer_cache: skip clean tracker words, refilled usage pages and repeated index binds (Nx2Perf)

- word_manager: an exact per-word clean bitmask (no CPU and no Untracked page), kept on every
  path that writes those words. SynchronizeBuffer walks only each manager's dirty words
  (ForEachDirtyUploadRange), a per-word form of PR zortos293#161's upload_words skip.
- usage_tracker: Track fills only the pages outside the run already known to be all ones.
- vk_buffer_cache: skip a vkCmdBindIndexBuffer that repeats the bound buffer, offset and type in
  the same scheduler recording epoch (from PR zortos293#161, commit 5a0cdcb).
- buffer_cache: check the per-frame native bank set before its GPU-modified query.

All behind Nx2Perf::Enabled(); EDEN_NX2_PERF_VERIFY=1 walks every word and checks the clean
bitmask against a recount, and checks skipped usage pages are all ones.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* memory_manager: answer MaxContinuousRange and GetMemoryLayoutSize from runs kept at map time

Replaces the EDEN_NX2_RUN_CACHE experiment. Each page table operation rebuilds the maximal
device-contiguous runs through the big pages it touched, from the tables themselves; readers
query an immutable snapshot (runs plus a copy of the kind map) that each thread swaps only when
map_serial changes, so the hot path takes no lock. Behind Nx2Perf::Enabled(); off is the plain
walk and the kind map under its lock, and Nx2Perf::Verify() checks every answer against them.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* vk_pipeline_cache: keep sixteen sessions of used pipelines and save the list every 15 s (zortos293#161)

From 999sian's PR zortos293#161 (88ffd1f), behind EDEN_NX2_PERF_OPT. The runtime
eviction age stays 9000 frames: it only acts after that many frames, so it is
not what builds pipelines early in play.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: cheaper Maxwell3D method dispatch on the GPU back thread (Nx2Perf)

With VideoCommon::Nx2Perf::Enabled() (EDEN_NX2_PERF_OPT, default on) and no write history,
first-write trace or GPU profile:
- Maxwell3D::CallMethod takes CallMethodFast for state and executable writes outside macro
  parameter collection: no RecordWrite/trace/profile/macro checks, and draw begin/end, constant
  buffer data, bind groups and the four executable methods the draw manager ignores go straight
  to their handler. Everything else, and the switch off, runs CallMethodSlow (the old body).
- GpuSplit::Execute calls Maxwell3D directly (it is final) without profile zones, and runs a
  row of single constant buffer data entries (macro uploads) through
  Maxwell3D::CallConstBufferWords: shadow RAM, dirty flags and deferred draws per word as before,
  one memory write, and the invalidation accumulator fed word by word as WriteBlockCached did
  (MemoryManager::WriteWordsCached). Rows that would be clipped, sparse memory and macro parameter
  collection go word by word.
- EDEN_NX2_PERF_VERIFY=1 runs the old path for state writes and constant buffer rows too, logs
  "Nx2Perf method:" differences and keeps the old result.

No new same-value skips: every method a skip is exact for (non-executable state writes) is
already skipped on the front and back. Reviewed the executable-method dirty skip and front
constant buffer batching of PR zortos293#161 (7422d90) and did not port them: the skip only changes
index_buffer.first/count, where UpdateIndexBuffer needs Dirty::IndexBuffer after DrawIndexSmall
or inline draws; the batching leaves the data registers and their shadow RAM unwritten and
changes the accumulator ranges.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: split front waits only for the queued work an MME DMA read depends on

Builds on PR zortos293#161's sync-point model (FrontDmaRead waiting for synced writers, the
GpuSplit front_wake sleep) and closes its two holes. The front tracks what each
queued entry may write (SplitWriteTracker): unknown (puller releases, I2M/inline
uploads, unclassified methods), GPU work (draws, dispatches, copies, blits), or
nothing besides noted ranges (constant buffer data, 3D and compute reports, inline
QMDs). FrontDmaReadEarly waits for every unknown write, the GPU work before the last
wait for idle (3D wait_for_idle/trigger_wfi, compute WAIT_FOR_IDLE, puller
WaitForIdle/fence acquire, GP sync bit) and the noted writes to the read's pages,
then reads guest memory. Pages ever written by GPU work, sparse or unmapped pages
and queued GPU map changes fall back to the drain. Semaphore acquires keep their
full drain and fence release; zortos293#161's CanResumeAcquire shortcut is not ported.

All of it is behind EDEN_NX2_PERF_OPT; EDEN_NX2_PERF_VERIFY drains and re-reads,
logs differences as "Nx2Perf split:" and uses the drained words.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* nx2_perf: EDEN_NX2_PERF_OFF=<name>,... turns single optimizations off; the state is logged once

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: one GPU-written lookup per buffer write, heap-free tracker batches, skip empty gathers (Nx2Perf)

BufferCache::WriteMemory looked up gpu_modified_ranges twice, for the written range and for its
pages. The range lies within its pages, so when the pages hold no GPU-written data one lookup
answers both. WordManager collected and merged changed page runs in two std::vectors, and
DeviceMemoryManager::UpdatePagesCachedBatch copied, sorted and merged them again into two more;
the runs now stay on the stack, are merged in place, and the batch takes runs that are already
sorted with gaps between them as they are. GPUDirtyMemoryManager::Gather, run at every fence,
returns after its gatherer lock when nothing was collected instead of taking the collectors'
lock and swapping empty buffers.

All behind EDEN_NX2_PERF_OPT; EDEN_NX2_PERF_VERIFY=1 runs the old paths and logs any difference.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: preserve disabled tracking path and drop ineffective gather shortcut

Keep the original std::vector when collecting tracker ranges so the off switch preserves the old path. The enabled path still eliminates the three downstream vector copies. Remove the gather shortcut: IsValid takes a 39-bit byte-address bound but its caller supplies a u32 page number, so its empty predicate was never true. Leave gather behavior unchanged rather than widening this optimization into a concurrency fix. Exercise the production tracker merge in the differential test, and run the same tests enabled, disabled and in verification mode.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* nx2_perf: independently gate write intersections and sorted page batches

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* vulkan: retain deferred fragment barriers across unrelated draws

Do not consume the pending dependency while checking an unrelated draw. Clear it only when EndRenderPass records the barrier. Keep the existing performance switches and dependency policy unchanged.

Add the deferred-barrier regression plus SlotVector relocation/reset and MemoryTracker manager-pool growth equivalence tests.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: keep batched CB writes within one device page

Preserve per-word translation and range rejection when CB data crosses a 4 KiB device page. A bulk write can otherwise use a stale GPU big-page continuity bit after a device remap, or drop a valid prefix when the run crosses the GPU address-space limit.

Exercise actual GpuSplit Method/MacroWrite/MultiMethod/Defer encoding and Execute with mapped backing, publication limits, ring wrap, engine switches, shadow replay, deferred draws, and buffer clipping. Both boundary regressions fail without the guard and pass with it, including verify mode.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: quarantine unsafe early MME DMA reads

Audit the PR zortos293#161-derived split-overlap model on perf/oot-combined 7ea801a. A ring entry classified as GPU work can perform an immediate CPU copy or enqueue the first write to a previously unmarked page. Capping its target at the last sync lets both page checks pass before that entry executes. The new adversarial schedules fail on the old target.

Remove the sync cap and its unused engine-sync state. The randomized oracle now requires every unlocalized writer, independent of guest sync assumptions. This fixes the demonstrated target misses, but consumption still does not prove completion of fence callbacks, and device-page identity is not a complete physical-alias proof.

Keep split_overlap forcibly off, including in Verify mode, and log that state. MME DMA reads retain the full drain and existing buffer-cache flush. Remove the unsafe EDEN_NX2_DMA_NO_DRAIN bypass. FrontSleep and all unrelated optimizations remain independently selectable. This is a correctness quarantine, not a claimed fix for the reported foliage or descriptor corruption.

Validation: clang-20 tests build; 7 split-write cases / 32625 assertions; 350 video_core cases / 60739787 assertions. Quarantine regressions also pass with PERF_OPT=1, PERF_VERIFY=1 and the obsolete DMA_NO_DRAIN variable present, and with PERF_OPT=0. No PC or game run.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: defer native graphics SPIR-V emission to pipeline workers

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: harden async pipeline completion and teardown

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: bound owned-input unswizzle work on the texture pool

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* video_core: promote tested perf9 paths and remove unsafe overlap experiment

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* buffer_cache: promote perf9 buffer paths and remove candidate switches

Retain fe0c73f ON behavior for dirty-word scans, usage runs, index rebinds, native bank checks and sorted page batches. Keep diagnostic verification and the existing native read-trap/preflush default; remove its experimental override.

Drop only switch-interface test coverage and Enabled assumptions, retaining behavior-equivalence tests.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci: gate nx2 pull requests on GCC production builds and tests

Adapt the Linux dependency and Qt recipe proposed in PR zortos293#169 without merging that branch. Pin actions, disable checkout credentials, require GCC 13 and Werror, build the Qt frontend and unit tests, and cover nx2 pull requests, pushes and manual runs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: nyakuoff <bertrandbridou1994@gmail.com>
…tos293#193)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
)

Port the reviewed Frizti buffer, submission, pipeline-cache and FSR3 changes, including the tested host-read visibility and interrupted-migration corrections. Preserve conservative synchronization fallbacks and keep frame generation off by default.

Source: Frizti/Lichii oot-gather-fix at 765659a. Original contributions and commit provenance by Matt Artist, chuppatricky, Dr. Sergio and zortos293 are recorded in PR zortos293#194 and its port commits.

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Port the default-on built-in Add-On from Frizti's 765659a with verified original-byte, executable-padding, BSS and transactional user-mod guards. Preserve Dark's routine and existing Add-Ons disabling.

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…mpling

shader: fix AMD depth sampling, atomic retries and compiler stalls
* shader: fix AMD shader typing, atomic retries and compiler stalls

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* externals: port upstream MinGW AV1 package selection

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ompute writes (zortos293#295)

* shader: split misaligned 64 and 128-bit storage buffer accesses into words

Storage bindings start at the device's minStorageBufferOffsetAlignment (4 bytes on AMD), and
the shader's offsets are relative to that base. An aligned 8 or 16 byte guest access can then
start at any word of the binding, and indexing the vector view (offset >> 3 or 4) read and
wrote the vector before it, which shifts Ocarina's light colours (green lighting, cyan sky).

Direct storage accesses keep the vector view only when the offset is known to be a multiple
of the access size; global accesses find the buffer once by the start address, as the vector
path did, and read or write its words. Without descriptor aliasing the per-word global path is
unchanged.

Ported from mja00/Lichii fix/ocarina-of-time: 5e8d409 (Matt Artist, after Selevet fix 36)
and c418e4f (sharkiix, from sharkiix-v2).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* vulkan: order compute writes before later draws, indirect reads and idle waits

The barrier before a dispatch covered the compute stage only, so indirect arguments written
earlier were read unordered. Nothing ordered a dispatch's writes before the draws of the next
render pass. The guest's wait-for-idle set an event and waited on it at TOP_OF_PIPE with no
memory barrier. TOP_OF_PIPE in the second scope is ALL_COMMANDS with no access, so the first
wait ordered execution but made no write visible. The event was never reset, and
vkCmdSetEvent on a signalled event generates no dependency, so later wait-for-idles did
not wait for work recorded after the first signal.

The dispatch barrier now includes DRAW_INDIRECT, the next render pass begins after a
compute-to-graphics barrier, and wait-for-idle is a pipeline barrier to ALL_COMMANDS with a
memory barrier.

Based on mja00/Lichii fix/ocarina-of-time c7b8715 (Matt Artist); the wait-for-idle uses a
pipeline barrier instead of that commit's event wait for the reasons above.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* common: synchronize stopped-worker notifications with waiters

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* tools/nx2: report thread_worker_wake setup failures as harness errors

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…nbuilt pipelines (zortos293#224)

* video_core: opt-in EDEN_NX2_ASYNC_DISCARD to skip native draws with unbuilt pipelines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* video_core: document the async draw policy and BuiltPipeline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* video_core: document BuiltPipeline and log skipped lookups, not draws

CodeRabbit: the counter counts pipeline lookups that returned no pipeline, which can exceed
the number of draws skipped. Name it accordingly; document the function and the test case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* video_core: doc comments for the async discard switch and its test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…tos293#314)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…os293#287)

* frontend: fix Linux AppImage switch discovery and package defaults

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* frontend: constrain adjacent switch files and reject relative config roots

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* qt: add consent-first desktop compatibility reporting

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* tests: compare HTTP header names case-insensitively

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* qt: preserve report state and prefill active GPU identity

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* qt: prefill report title and enabled add-ons

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* qt: report the loaded NUP package version

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* qt: preserve literal characters in catalog searches

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…3#397)

* core: add the run walker the pending-write port uses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit eb6a2de)

* buffer_cache: don't download GPU data over newer CPU writes

Ported from Duck912/Eglantine-ducky e27fbc6b03 (Duck912). CPU writes into
GPU-written memory are collected and applied later by the GPU thread;
until then two paths wrote the GPU's older copy over them: the fencing
thread's async download pops, and applying the collected writes one by
one (WriteMemory downloads the GPU bytes elsewhere on the pages it
touches). In Ocarina this made the Z-target reticle use values from two
frames before on about half the frames.

Every download write into guest memory now skips the collected,
unapplied CPU writes (GPUDirtyMemoryManager::ForEachPending, which here
also covers writes a Gather is handing out), and InvalidateGPUCache hands
the collected writes to the rasterizer as one batch that is taken out of
the GPU-written ranges before any is applied. This tree has more download
paths than the original (DownloadMemoryRanges, DownloadRangesMemory); all
eight writes go through the same check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0c82282)

* tests: downloads keep CPU writes collected and not yet applied

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* shader: gather from a second descriptor buffer as TLD4.B

From MateusAugustoBrito/Lichii a04f756 (MateusAugustoBrito). A gather
whose handle comes from another descriptor buffer than the program's
loads it with MOV and emits Maxwell TLD4.B, as TEX already does, instead
of rejecting the program. Programs with one descriptor buffer lower as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 794233f)

* tests: gathers from a second descriptor buffer lower to TLD4.B

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…tos293#185)

* texture_cache: land deferred clears before non-pass image writes

A deferred clear is parked until its framebuffer's next render pass, but
texture cache copies bypass render passes: the clear then realizes after
the copy and wipes what the copy wrote. On hardware the clear executes
at its stream position, before the copy, so the copy wins.

That is the Ocarina of Time terrain layer (zortos293#101): the game clears the
occlusion depth, blits the half-res scene depth over it, then the layer
draw samples it. The parked clear landed after the blit from the second
frame on (GetFramebufferId only flushes when it creates a framebuffer),
the map read 1.0 everywhere, and the layer drew over the scene. It
matches the workaround that dropping the Z-only clears before the blit
removes the blotches.

BlitImage, CopyImage and PrepareDmaImage now realize a parked clear
before writing; draws keep the load-op fusion.

* texture_cache: only land deferred clears when the write proceeds

A rejected copy writes nothing and must not realize a parked clear ahead
of the next draw that consumes it as its load-op. Move the flush in
BlitImage after GetBlitImages (safe: framebuffer removal flushes first),
after CopyImage's early returns, and gate PrepareDmaImage's on the DMA
write actually happening (mark_as_modified).

Review note from RomainROCH on zortos293#185.
…SURED register shadowing (zortos293#149)

* vulkan: submit deferred work before waiting on pending fences

On the split GPU thread, Scheduler::DeferFlush holds fence submissions back to batch them; the
back submits them only when idle or asked between entries. WaitPendingFences(force) on the back
queued a fence and blocked on the fencing thread, which waited for that fence's submission:
Ocarina of Time froze on a black screen as gameplay began. Submit deferred work before waiting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* present: interpolate between the previous and current frame in FSR3 frame gen

Process() copied the new frame into both the history and current images after the flow, so every
generated frame blended the newest frame with itself; the interpolation also applied the motion
vectors with the wrong sign and as full-resolution texels. Text over moving backgrounds was mixed
with its own drop shadow (black title menu options in Ocarina of Time).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shader: shadow the data registers of RED and SURED

The register shadowing check reads bits 32-39 only for register-form
opcodes and a short exception list. zortos293#100 added ISBEWR (0x927) for
Ocarina's door NaN. RED (0x98e) and SURED (0x59f) take their data
register from bits 32-39 as well, and RegisterPressure already lists
operand 32 for both, but they were never asked: a data register at or
above the uniform bank read Lichii's UR mirror instead of the guest
value. Add them alongside ISBEWR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e rejecting (zortos293#241)

A native program whose own registers reach into the high uniform mirrors
(R(216 - n) and up) has those registers shadowed by PrepareRegisterPressure,
which works in aligned groups of four. n counts in pairs, so the base could
be misaligned (R202 for n = 14) and the program was rejected with "native
program uses lowering scratch registers" even though shadowing supports its
instructions. Request one more lowering pass with n rounded up to a multiple
of four instead; programs that lowered before are unchanged.

Also shadow the register form of SHF (0x219): its destination, low word,
shift amount and high word, like 0x210-0x212. Programs with it there were
rejected with "native register shadowing mode".

Shadow LOP3.LUT with a constant buffer or uniform B (0xa12, 0xc12) like its
immediate form (0x812) too: Rd, A and C are its only registers.

ampere_ported_uniform_vote expected the old rejection for a native R214 inside
the UR28 mirrors; it now checks the realigned result instead: UR28 read at
R212 and the native move written to its shadow, R210.

In Ocarina of Time both rejected pixel shaders of the Gerudo Valley rock
draws, so the rocks were skipped and the sky showed through.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rray index against the innermost array (zortos293#203)

* fix(file_sys): apply LayeredFS mods to NUP titles

AppLoader_NUP reports the RomFS as not updatable (update already merged),
so RomFSFactory returned the raw file and user/load/<title>/<mod>/romfs
layers were never applied. Run PatchRomFS without an NCA: the update step
is skipped and LayeredFS still runs. No mods present = same file as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(config): assert nested array index against the innermost array

SetArrayIndex compared the index with the outermost array's size, so a
nested array (DisabledAddOns/<n>/disabled with more than one entry) hit
the assert on every entry past the outer size. Reading still worked; only
the log filled with Critical asserts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…arina white flashes) (zortos293#215)

* video_core: flush the query cache before macro DMA reads under the GPU split

Macro DMA reads of report regions flush the query cache so occlusion
reports and semaphore writes land before the read, but the flush was
gated on the split GPU thread being inactive. The default configuration
(EDEN_NX2_SPLIT=2) therefore served stale counts to macros whose own
comment warns that stale values make 'occlusion predicates read stale
values' -- the guest reads a zero visibility count and culls visible
objects, which matches Ocarina of Time's missing vegetation (zortos293#101) and
the count=0 draws traced there.

The native macro path now flushes under the same owns_caches condition
as its stale-read trace, and the split front path (FrontDmaRead) flushes
on the back next to its buffer cache flush. SemiFlushQueryDirty writes
out finalized values directly and waits out the rest through the fence
manager (WaitPendingFences, hardened in zortos293#149).

Build-checked and unit-tested on Linux; gameplay validation needs the
EDEN_NX2_SPLIT=0 vs 2 vegetation A/B described in zortos293#101.

* video_core: wait for pending query results before macro DMA reads

The query cache flush before macro DMA reads asks the fence manager to
wait out pending reports, but below High GPU accuracy a fence runs its
callback at once, so the wait returned before the report writes landed.
The macro still read the old occlusion counts. With the GPU split's
EDEN_NX2_QUERY_DMA_TRACE on, Ocarina of Time (0400486024DEC000) showed
976 such reads in 2.5 minutes, each reading a different value than the
one that landed later, and the scene flashed overexposed for a few frames
whenever the camera moved (cutscenes or the stick).

The macro paths now flush through FlushQueryResults: when the region
holds pending reports, Vulkan queues an operation behind them, signals a
fence, submits work the split's back deferred, and waits until the
operation has run. Other ReleaseFences callers are unchanged.

Build-checked on Linux (clang, Release). Gameplay tested on NVIDIA, Low
GPU accuracy, GPU split active: the flashes on camera movement are gone.
Not run: unit tests, AMD, High accuracy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Gavin Schiller <gavinschiller51@gmail.com>
Co-authored-by: WeslieDE <weslie@weslie.tk>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ortos293#197)

* shader_cache: skip the cached rejection path for compute dispatches

A rejected program fails the same way on every dispatch, but ComputeShader()
rethrows the recorded exception and then records it again: two C++ throws, a
mutex-guarded insert and a current_exception() each time the program is
dispatched. GetGraphicsEnvironments already skips the equivalent draws for
exactly this reason ("two C++ throws each time cost ~1-2% of the GPU thread"),
so give the compute path the same treatment.

Return a missing shader instead, which CurrentComputePipeline() in both
renderers already handles, and keep the first exception recorded for an
address rather than capturing a new one on every rejection.

Reported against build-75 (c9ebb5c): Ocarina of Time rejects compute programs
with "native texture gather mode" and "native per-tap gather control-flow
proof" and then dispatches them repeatedly, so the compute path paid this cost
per dispatch rather than once.

* renderer_opengl: report a null native compute pipeline as a failed dispatch

Native mode is enabled from the command stream (ChannelState::Init, gated by
g_nx2_ampere), not by the renderer, so the OpenGL rasterizer can reach the
cached-rejection path that now returns a missing ComputePipeline. Returning
silently there makes KeplerCompute::DispatchNativeCompute treat the dispatch
as successful, which skips its rejection bookkeeping (including
dlss_chain.rejected) and reports no QMD failure.

Match Vulkan and throw instead, as both backends already do for a native
compute pipeline that cannot be built.

Reported by CodeRabbit on zortos293#197.

---------

Co-authored-by: axsddlr <axsddlr@users.noreply.github.com>
…GPU back thread (zortos293#412)

* vulkan: look the draw's pipeline up once

UpdateDynamicStates looked the current graphics pipeline up again for each draw
(~2.5% of the split's back thread in Ocarina); PrepareDraw now passes the one it
found.

Ported from Frizti/Lichii 23f40d5 without its tools/nx2/sample_report.py
change (nx2 has no sampling profiler report).

(cherry picked from commit 23f40d5)

* common, query_cache: skip range subtractions and query erases that touch nothing

RangeSet::Subtract looks the range up first: the buffer cache clears several sets on every
write, and most hold nothing there. IterateCache only erases a page's queries when one was in
the range. About 3% of the split's back thread in Ocarina of Time (Lake Hylia).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit da215e8)

* video_core: skip resetting untouched buffer usage trackers

Port the tracked-state guard from sylvieistaken/selevet-eglantine d8c95fe. Keep page and recent-range clears after every Track while avoiding repeated clears for idle buffers. Preserve existing tracker ownership and reset semantics.

(cherry picked from commit b16ba4f)

* tests: cover RangeSet subtraction and skipped usage tracker resets

RangeSet::Subtract now returns early when nothing intersects the range, and
UsageTracker::Reset skips trackers nothing tracked since the last reset.
Check both against reference models: a per-address bitmap for the range set,
and the always-clearing tracker for resets, including back-to-back resets.

---------

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…r-GPU-written pages (zortos293#413)

* vulkan: skip the buffer cache lock for dirty checks on pages the GPU never wrote

The split's front asks MustFlushRegion before each macro DMA read; it waited for the
buffer cache mutex the back holds through each draw (8% of the front in Ocarina).
Every GPU-modified range also marks its pages GPU-written (bits that never clear), so
pages without the bit are answered without the lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6247fd5)

* video_core: answer the lock-free flush check from nx2.h and test it

Move the GPU-written page walk behind MustFlushRegion's lock-free answer into
Tegra::Nx2MayBeGpuWritten next to the bitmap it reads. A range past the bitmap,
or one that wraps, now answers true and takes the lock: Nx2MarkGpuWritten clamps
to the bitmap, so those pages were never marked. Test the page boundaries, the
bitmap end and clearing.

---------

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…93#414)

From Hydro74000/Edlws 034668a14e (Hydro74000). The old buffer is kept
until the GPU is done with the current tick instead of finishing the
GPU, and the buffer grows by at least 1.5x.


(cherry picked from commit a20c9a9)

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…(OoT near foliage) (zortos293#410)

* shader: bind loaded pointers that compete with a paired cbuf pointer

A native global access whose address is a select or phi of a constant
buffer pointer and a pointer loaded from that buffer was paired with the
constant buffer pointer alone, so the native candidate search never ran.
Accesses through the loaded pointer were then rebased on the constant
buffer's binding and read out of bounds as zeros.

Ocarina of Time reads its foliage unit placement records from c0[12] or
from *c0[12]. The near-leaf pass (539ffb3f650a34ac) read every record as
zero, normalised a zero vector and wrote NaN positions, so the near
ShootLeaf meshes (BancLeafMesh, FoliageLeafNear) were never built and
trees lost their leaves inside about 20 m.

Run the candidate search for paired accesses too. If a loaded pointer
competes with the pair, bind every candidate plus the paired pointer and
let the backend pick the buffer at run time; otherwise keep the pair.

Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
(cherry picked from commit a51a69f)

* tests: bind both a cbuf pointer and the pointer loaded from it

A native access whose address is a select or phi of c0[0x30] and a
pointer loaded through c0[0x30] binds both buffers and stays global, as
Ocarina of Time's near-leaf pass 539ffb3f650a34ac needs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* shader: keep the pair when competing loaded pointers cannot be bound

Binding the loaded pointers that compete with a paired cbuf pointer
leaves the access global. The SPIR-V backend cannot route global subword
accesses or most global atomics, so those accesses keep their pair.

The loaded pointers are bound without the table budget, so a program
already at 32 storage buffers overflowed the descriptor vector. When
they leave too few bindings, collect again keeping the pairs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* shader: keep the pair without 64-bit integers

The SPIR-V backend routes no global access among bound buffers without
shaderInt64, so a paired access left global would read zeros or fail to
translate.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
Co-authored-by: Duck912 <350528965+Duck912@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… CPUs on Windows (zortos293#232)

SetCurrentThreadToPerformanceCores(), SetCurrentThreadToEfficiencyCores() and the other
placement calls only did something on Android. On Windows a hybrid CPU (Intel P and E
cores) could run the CPU core threads, the GPU front and back threads, the Vulkan
worker, HostTiming and the fencing thread on an efficiency core, where a frame takes
noticeably longer, while the texture decoder and pipeline builders competed for the
fast cores.

Read the CPU sets with GetSystemCpuSetInformation, take the highest EfficiencyClass
as the performance group, and assign each thread its group with
SetThreadSelectedCpuSets (a soft affinity the power manager respects, unlike affinity
masks). Non-hybrid CPUs (one efficiency class) are unchanged; EDEN_HYBRID_CORES=0
turns it off. Both APIs are looked up at run time so older Windows builds still start.

On an i9-13900K (8 P + 16 E cores) the time-critical threads now show the 16 P-core
CPU sets selected and the texture decoder the 16 E-core sets (verified from outside
with GetThreadSelectedCpuSets). Ocarina of Time in Kokiri Forest measured within
noise of the previous placement (Windows already favoured the P-cores for those
threads in that scene), so this is parity with the Android placement and a guard
against the scheduler moving a hot thread, not a measured speed-up.
* video_core: fix GCC shadow error in pipeline cache

The deferred shader pool local reused the name of the CreateGraphicsPipeline parameter. GCC treats this as a shadow warning, and Linux builds with ENABLE_WERROR=ON fail because -Werror=shadow promotes it to an error. Rename the local while keeping pool release and reuse unchanged.

* tests: fix GCC shadow errors in Linux build

Recent config, audio, and video tests introduced local names that shadow inherited members or the global Result type. With ENABLE_WERROR=ON, GCC treats those warnings as errors and stops the full build. Rename the test locals without changing their behavior.
MSVC builds with warnings as errors stop on C4388/C4389 in
nx2_debug_server.cpp (std::optional<u64> == int) and
buffer_cache_ports.cpp (u32 == int).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…93#167)

swscale defines only the visible width of each output row: the bytes up
to the stride are spill from its vector stores and can differ between
two identical conversions. The scaler-path tests compared full pitch
rows, so two of them fail on FFmpeg 9.0 with 24 differing bytes that are
all in the chroma row padding past the output width (the visible pixels
are bit-identical). Compare the visible pixels instead. The hand-built
copy-path expectations stay byte-exact.

Build-checked and tested on Linux with FFmpeg 9.0.2: all 674 test cases
pass, including the two that failed before.
zortos293 and others added 30 commits November 8, 2026 08:06
…cores (zortos293#698)

zortos293#232 keeps about ten time-critical threads on the performance processors. On an i5-1235U
(2 P cores, 4 logical) that crowded them: Ocarina of Time in Kokiri ran at 9.3 FPS, 11.5 FPS
with EDEN_HYBRID_CORES=0. Place threads only with 8 or more performance logical processors.


Claude-Session: https://claude.ai/code/session_01H8tHS3QiPU1NNDEpHaQTfr
(cherry picked from commit fb0b747)

Co-authored-by: AxelBcr <178737868+AxelBcr@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…uery walks early (all games) (zortos293#700)

* video_core: invalidate whole blocks, stop query walks at the first dirty query

From nemyu 99c953389 (Uesleizito Zin): the invalidation accumulator took a
write's size before aligning its address down, so a write past the start of its
32-byte block got a range ending short of it, and storage buffers, textures and
shaders over CB_DATA or inline-upload words kept stale data.
EDEN_NX2_INVALIDATE_BLOCKS=0 rounds as before.

From Orchid d38d025: IterateCache tested std::invoke_result (the trait, never
bool), so IsRegionGpuModified never stopped early. SemiFlushRegion (Lichii
zortos293#215) no longer returns from its callback, so it still writes every final value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b3ce9f1)

* video_core: drop the EDEN_NX2_INVALIDATE_BLOCKS switch, test the accumulator ranges

The previous commit (cherry-picked from ProphetWild's fork) kept the old
rounding behind EDEN_NX2_INVALIDATE_BLOCKS=0 for comparisons. Experiment
switches don't ship in nx2, so the whole-block rounding is now unconditional.

The tests pin the ranges: a lone word at 0x54 of its block, a run of words
starting mid-block, and a write spanning blocks. All three failed before
the fix (a word at 0x1054 gave [0x1040, 0x104c), which misses it).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: ProphetWild <133422166+ProphetWild@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…et (Ocarina of Time) (zortos293#692)

EDEN_NX2_TC_EXPECTED_MIB / EDEN_NX2_TC_CRITICAL_MIB replaced the
collector's thresholds outright, so a value above what the device budget
allows kept the texture cache above a shrinking budget (other programs'
video memory, the out-of-memory margins) and the driver paged: drops to
9 fps on an 8 GB card in Ocarina of Time's Kokiri Forest. The overrides
now raise the thresholds past MaxCriticalMemory but never past the
budget-derived value they replace, and expected stays at or below
critical.

Ported from upstream Eglantine 5d5fba1 (Ale, merged in upstream zortos293#727).
Adapted: the TickFrame comment that said the overrides "still win" now
says they raise the thresholds up to the budget.

(cherry picked from commit 213442947234337d32040bf96cfe9e58e060445e)
(cherry picked from commit 5d5fba1)

Co-authored-by: Ale <ale@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…rer cmd 14 (zortos293#600)

* audio: return the registered managers from nx2:aud:u, add IAudioRenderer cmd 14

Switch 2 titles open audio through nx2:aud:u, which constructed a fresh
IAudioOutManager/IAudioRendererManager per call. A fresh manager owns its
own AudioCore manager, but AudioManager accepts only the first registered
buffer-release callback, so sessions opened through a second manager never
release buffers: the title queues silent buffers and its audio thread
stalls. Return the registered audin:u / audout:u / audren:u instances from
the service manager instead, falling back to a private manager with an
error log. GetAudioInManager gets the same shared-manager treatment.

Also add IAudioRenderer command 14 (SetAsyncMemoryPoolControlEnabled,
23.0.0+) as a logged success no-op; newer-firmware titles call it.

Fix diff supplied and tested by @javaflans in zortos293#539
(buffers released, audio plays). Adapted to current nx2. Not compiled or
re-tested here.

Assisted-by: Instinct (verification, adaptation)

* audio: return the registered managers from nx2:aud:u, add IAudioRenderer cmd 14

* audio: return the registered managers from nx2:aud:u, add IAudioRenderer cmd 14
IDeliveryCacheFileService::Open checked the file name with the directory rule, which refuses a
period, so every name with an extension failed with ResultInvalidArgument.
…tos293#642)

* vulkan: add overflow-safe storage descriptor set budget helper

* vulkan: include mesh emulation buffers in descriptor set limit

* test: cover mesh fallback descriptor set budgets on constrained GPUs
…zortos293#756)

HintCold runs every 30 s on the Switch 2 texture pool backing. On Linux it
called madvise(MADV_PAGEOUT), which writes the pages to swap right away
while holding the mmap lock. A guest core's mprotect (the JIT) then waits
for that lock, and the whole emulator stops with it, audio included.

Ocarina of Time on an i5-10400 with swap on a SATA SSD: the emulator
stopped for 3-15 s every few minutes, with nx2:ColdHint in rq_qos_wait and
a CPUCore thread in mprotect, every guest core idle.

MADV_COLD only deactivates the pages, like VirtualUnlock on Windows, so the
kernel pages them out first when RAM is short, as a03e816 intended.
With it, no swap-outs and none of these stalls over about 30 minutes of
play; the hitches left were pipeline builds.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…top reporting (zortos293#710) (zortos293#712)

* input_common: compare motion mapping input with its first value

Mapping a motion input ("Shake!") registers a device only when two consecutive
reports differ by more than 1.5 g or 0.6 rotations/s. SDL3 delivers every
sensor sample, so a controller reporting every 4 ms (the Steam Controller at
~250 Hz) never changes that much between two reports and could not be mapped
through the UI.

Compare each report with the first one received since mapping began instead,
so a quick turn or shake crosses the thresholds at any report rate. The
baselines are cleared when mapping begins and ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* input_common/sdl: restart motion sensors that stop reporting

The SDL driver restarts a sensor only when its reports are all zero. A Steam
Controller (2026, SDL's Triton driver) through its wireless dongle stops
sending sensor reports altogether after it reconnects or when Steam Input,
running at the same time, changes its IMU mode; SDL turns the IMU on only
once, so motion stayed dead until the emulator restarted, and the motion
mapping dialog had nothing to detect either.

Track when each controller last reported a sensor sample and, from
PumpEvents (every 250 ms), restart the sensors of a connected controller with
motion that has been silent for a second, backing off to 8 s while it stays
silent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… (Ocarina of Time) (zortos293#718)

zortos293#121 (5b89a3d) paused the ZPassPixelCount64 occlusion counter across
submissions and added a 1-bank (256-slot) reset bound in
SamplesStreamer::CloseCounter() so Ocarina of Time's title screen and
file select would not accumulate 16,384 slots across 64 banks before
gameplay started. zortos293#309 (a08bf8f) added a power-of-two warning log to
catch if a real gameplay reset-to-report window ever exceeded 256 slots.

During extended Ocarina of Time (0400486024DEC000) gameplay across
Hyrule Field, Kakariko Village, and the Shooting Gallery (with both Low
and High GPU accuracy, EDEN_NX2_SPLIT=2, EDEN_NX2_FENCE_BATCH=1), real
reset-to-report occlusion windows regularly span 257 to 346 slots across
two banks:

  Render.Vulkan <Warning> vk_query_cache.cpp:CloseCounter:229: Samples query ran 314 slots with no reset or report, dropped its count (drop 256)
  Render.Vulkan <Warning> vk_query_cache.cpp:CloseCounter:229: Samples query ran 277 slots with no reset or report, dropped its count (drop 1024)
  Render.Vulkan <Warning> vk_query_cache.cpp:CloseCounter:229: Samples query ran 281 slots with no reset or report, dropped its count (drop 2048)
  Render.Vulkan <Warning> vk_query_cache.cpp:CloseCounter:229: Samples query ran 343 slots with no reset or report, dropped its count (drop 16384)

Each drop zeroes a live ZPassPixelCount64 window mid-frame before the
guest's report reads it.

Raise the running-query bound in SamplesStreamer::CloseCounter() from 1
bank (256 slots) to 4 banks (1,024 slots), and fix the off-by-one
boundary check in queries_prefix_scan_sum.comp (	otal_work <
gl_SubgroupSize * LOCAL_RESULTS, since 	otal_work = accumulation_limit
= runs_to_do - 1 is the 0-based last slot index and a 257-slot dispatch
on 32-lane subgroups spans into subgroup 1 at slot index 256).
…s293#727)

Registers StubReplyZeroed handlers for nx2:vi2 command 201 and IGeneralService command 101. These commands are called during early boot on newer SDKs (including The Legend of Zelda: Ocarina of Time), preventing the emulator from crashing on unimplemented function assertions.

Fixes zortos293#653.
…rtos293#714)

EDEN_NX2_GPU_PROFILE, EDEN_NX2_CMDPAGE_DIAG, EDEN_NX2_DOWNLOAD_TRACE,
EDEN_NX2_PB_CHECK, EDEN_NX2_3D_METHODS and EDEN_NX2_SEM_WATCH were read
by namespace-scope initializers. Those run before main(), so before
LoadNx2Switches puts nx2_switches.txt into the environment: set in that
file, the switches did nothing, and only a real environment variable
turned them on.

The three header flags stay plain globals (Enabled() runs several times
per method write) and VideoCore::CreateGPU reads them again. The DmaPusher
and Maxwell3D switches become const members read when the channel is
made, like Maxwell3D's record_write_history.
)

* loader: prefer the en-us NUP title over the default title

Assisted-by: Claude Opus 5.5 (code changes)
Signed-off-by: Kabosune <59909498+Kabosune@users.noreply.github.com>

* loader: show the en-us NUP title in the library when there is one

ReadTitle returns display_title before it reads the NACP, so the en-us title has to win
there too.

Assisted-by: Claude Opus 5.5 (code changes)
Signed-off-by: Kabosune <59909498+Kabosune@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: Kabosune <59909498+Kabosune@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… collecting (zortos293#268)

4d8a962 capped the collector's critical threshold at 5 GiB on every GPU. On a 16 GB card
Mario Kart World's races then sit at the cap: the collector evicts render targets every frame,
which are recreated and uploaded from guest memory again (~4100 uploads, 5 GiB per 600 frames).
The cap is now 5/8 of the driver's memory budget, at least 5 GiB, so cards up to 8 GB are
unchanged. GetDeviceLocalMemory is not used: it is capped near 7 GiB.

Mario Kart World, Wario Stadium staff ghost replay, RTX 5080 (16 GB, critical 9.3 GiB), 2 runs:
53.0 -> 58.7 fps, p90 26.7 -> 18.4 ms, p99 45 -> 30 ms, GPU back thread 13.1 -> 11.0 ms a frame.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ortos293#676) (zortos293#721)

* video_core: replace Ocarina's movie network by an input copy (port of Eglantine 92f1191)

The movie network's emulated matrix multiplies take about 33 ms of GPU time per frame, which held the intro and cutscenes at 30 fps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* kepler_compute: limit the movie network replacement to Ocarina and handle stale input and failed copies

Addresses review feedback on the port: gate on the Ocarina program id, expire the retained input with the skip window, and fall back to the real network when the copy fails.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…euse, per-method switch reads (zortos293#778)

* tests: a remembered rejection marks its page in the shader cache's filter

From zortos293#408 (the shader_invalidation.cpp part of ba882cc), for
the zortos293#250 port above, which takes the RememberRejection mark from the same
commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 16b4992)

* tests: cover the K=16 software HMMA loop's operand halves and rounded stores

From zortos293#408 (the spirv_software_matrix.cpp part of ba882cc),
for the zortos293#174 port above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e30e573)

* tests: cover the page filter and the Ocarina of Time network bypass setting

zortos293#250 and zortos293#319, ported above, came without tests. The page
filter tests check marks across pages and 64-page words, empty marks, and the
conservative answer outside its 64 GiB range. The bypass tests check the
tonemap shader hashes and that the bypass follows the setting and the title.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit ad1c0f6)

Taken from zortos293#408 (ad1c0f6); applied cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c63b0b1)

* fence_manager: with the GPU profile on, log why fences are made instead of skipped

Ocarina's Castle Town: 6.1-7.7k fences made a second, 4.3-5.4k of them for committed cache
flushes (preflush downloads) and 1.8-2.3k for query cache fence work; 6.3-8.4k skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d6fb9f5)

* debugger: speed_limit method lifts or restores the speed limit live

Test scripts boot through the logos and menus unlimited, then restore it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5b562f8)

Adapted: nx2 has no live switch method to sit beside, so only speed_limit is added, and
docs/nx2/debug-mcp.md documents it.

* video_core: read EDEN_NX2_SKIP_SAME and EDEN_NX2_PERF_VERIFY once per Maxwell3D

From zortos293#488 (9010ecd, mstampfli), second half: SkipSameValueWrites()
and Nx2Perf::Verify() run for every 3D method on the back and kept their value in a
function-local static, so each call checked its initialization guard (0.9% and 0.3%
of the back thread in their profile).

Adapted: the PR reads both into namespace-scope statics at static initialization,
which runs before main() loads nx2_switches.txt (LoadNx2Switches), so a switch file
setting either would be missed. Here Maxwell3D reads them in its constructor into
members. Other Nx2Perf::Verify() callers are unchanged.

Not taken from zortos293#488: its same-value drop of vertex_buffer.first/count (already done
by our port of zortos293#178, bd27dc7) and of index_buffer.first, which the front sends on
purpose (it sets Dirty::IndexBuffer; our EDEN_NX2_EXP2 is the guarded version).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4f9a471)
Adapted to zortos293#714: the two switches are const members with default initializers, read when the
engine is made like trace_first_method_writes, instead of assignments in the constructor. nx2
has no FrontDmaReadEarly, so that hunk is dropped.

* video_core: bind no texture for a handle word that is a storage buffer address

Ocarina of Time's water material (shader 68d269bc849da37c, the Zora's River
waterfall and other water) samples a light's texture in a clustered light loop:
the handle is loaded per pixel from the light buffer, so no draw-time handle
program can compute it. The translation then falls back to the first constant
buffer word on the handle's path, c0[0x520], which is the light buffer's
address. Its low bits named an unrelated texture (an HDR render target, a zero
water simulation map), and the game double-buffers that buffer, so the texture
changed every frame and the water flickered every other frame in bursts.

A texture whose handle word is where a storage buffer of the same program keeps
its address now gets the null view and sampler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d603d98)

Adapted: unconditional. The fork's EDEN_NX2_EXP21 switch (default on) is dropped, since nx2
doesn't merge experiment switches.

* video_core: keep two idle upload staging buffers per level up to 32 MiB

Walking through Hyrule Field in Ocarina of Time, single uploads of new 2D images held a draw's
setup for 2-68 ms. Upload staging buffers are now kept, two idle per level up to 32 MiB (under
128 MiB), instead of being freed at each 64-frame sweep: uploads over 8 MiB miss the stream
buffer and got a new dedicated allocation after every pause.

This is bit 1 of the fork's EDEN_NX2_EXP8 (7d75f91, on by default there), taken alone and
unconditional. Its other bits (skipping uploads a join copy overwrites, banded unswizzling, no
HintCold) are not taken.

(cherry picked from commit 7d75f91, staging pool part only)

* tests: a page filter mark past its space leaves the tracked range untouched

The page filter test imported from the fork (c63b0b1) expects the first version of zortos293#250,
where a mark past the 36-bit space set an overflow flag that made every query answer yes.
nx2 has zortos293#250's later commit 65e3a09, which leaves such ranges untracked so images
without CPU backing don't switch the filter off. Check that behaviour instead.

* fence_manager: no branch hint on the fence decision profile check

docs/Coding.md leaves branch likelihood to PGO.

---------

Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: ZeroOneTV <wwwteko@hotmail.com>
Switch 2 compute storage pointers have no size, so a written binding was
marked GPU-modified for up to 8 MiB. Ocarina of Time's skinning dispatches
write tens of kilobytes; the rest of that range is the CPU's own per-object
and exposure data. Fences then copied the whole range back, and the CPU's
later writes to those pages waited for the next fence.

The recompiler now proves an affine bound on each store, and a dispatch
marks only those bytes. An unproven store still marks the whole binding.
The binding size is unchanged, so reads are unchanged.

Rebased onto build-117 from zortos293#415, without the Auto/Full/Cap64K
setting: unknown extents already keep the previous full-binding mark.

Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Copy the proven nup-romfs-windows workflow onto this branch so a push
packages lichii.exe without publishing a release. The binary is tagged
pr4-write-extent and the job also runs for cursor/oot-kokiri-playable-*.

Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Outdoor Kokiri on the write-extent build still spends about 300 ms of every
second in macro DMA read drains, about 30,000 reads a second of a few dozen
bytes, with buffer downloads already at 0. Those reads are the occlusion
report pool. The front drained the whole back thread before each one, including
state methods that cannot change the report.

Report and semaphore pages are now noted as the front sends them. A read of a
page in that pool, which shaders and copies have not written and whose host
query has already landed, is taken from guest memory plus any queued release
whose payload the front knows. Anything else still drains, as before.

The report address is address_high/address_low, after the compare block.
EDEN_NX2_DMA_READ_CHECK=1 drains as well and logs reads that differ.

Ported from zortos293#243, which measured this as the occlusion path
(~30k reads/s) and saw no differing reads under the check.

Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Outdoor play after the occlusion-report fast path still spends about 160-490 ms
of every second draining the back thread on semaphore acquires, about 1,600
blocking drains a second. zortos293#337 is not in this branch, so those drains are not
the payload-fence wait that change introduced. Each poll drained the back even
when the word was released by the CPU and no GPU release was queued.

A read now waits only until a queued GPU release of that word has run. When
none is queued, the host write has landed and the buffer cache is clean, the
word is taken from guest memory. A value that already matches is not trusted
while a release is still queued, which is the stale >= case. Anything the
buffer cache or an unfinished host query may still hold drains and flushes as
before.

Co-authored-by: xTektor <xTektor@users.noreply.github.com>
…locking the front

Outdoor play after the acquire fast path still spends about 200 ms/s waiting
for the GPU to finish the copy of a command list, and about 275 ms/s in the
writer wait before that read. Both GPU threads stand still for the copy. The
lists are still only read after the GPU wrote them.

A copy that is not finished suspends that channel, the same way a semaphore
acquire does. The scheduler runs the other channels and the queued work, and
the channel parses the entry again once the copy's tick is done. Resuming
skips the writer wait: it already ran, and waiting again would also wait for
work other channels queued during the suspension. If the step returns before
that read, the skip is cleared.

Co-authored-by: xTektor <xTektor@users.noreply.github.com>
The ec8e93f Legion outdoor Kokiri profile still spends about 378 ms/s
of the front thread in the writer wait (list writers ~284 ms/s) before
command entries are parsed, plus ~193 ms/s of pending sleep. Every entry
on a page no GPU work had written waited for every queued compute and
copy writer, and that wait usually left the back with nothing queued.

An entry that begins between methods (method_count == 0) is a command
buffer the CPU writes: the words GPU work writes for a later fetch are
parameters of a method begun in the entry before. Such entries no longer
wait for compute work. Entries that begin inside a method's data still
wait, and every entry still waits while copy-engine work is queued
(DmaPusher notes the last MaxwellDMA writer position), because a copy
can place GPU-written words anywhere. The wait that remains stops at the
work sent before the last host-level sync (NoteSync): the GPU only
orders a fetch after earlier work across such a wait.

Lists read from GPU-written pages wait the same way, under a separate
"list copy writers" drain site, so the next profile can tell them apart
from entry waits. A list whose copy is not finished still suspends the
channel and resumes through the existing Pending path.

Before the GPU thread sleeps on an empty queue with suspended channels
it now publishes the words it has sent, so the back is not left idle
for the whole sleep.

Not measured on the Legion yet; no FPS claim.

Ported from zortos293#805 (commit d4e8d85 by jpkto, plus the
publish-before-sleep piece of that PR):
zortos293#805

Tests: command_entry_wait (new), async_report_settled, semaphore_acquire,
list_read_resume linked standalone against Catch2 and pass; touched
translation units compile with GCC 14.

Co-authored-by: jpkto <255363096+jpkto@users.noreply.github.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
… data downloads

Ported from javocsoft#17 (commit edab3cc, "texture_cache: keep
transcoded textures in host memory, skip dead GPU data downloads"):
javocsoft#17

- TranscodeCache keeps CPU-transcoded texture data (BC1/BC3 or RGBA8) by a
  hash of the guest data and layout, up to a sixteenth of host memory
  (256 MiB to 2 GiB), least recently used first. The synchronous upload
  path and the async decode path both use it; an async hit is uploaded at
  the next tick without the zero-filled placeholder.
- The texture collector no longer downloads a GPU-modified image whose
  bytes newer GPU writes cover whole (HasDeadGpuData over its overlaps and
  aliases, RangesCover); it is evicted without the download.
- EDEN_NX2_TC_LOG also reports real allocations, GPU-written and rescaled
  shares, transcode cache hits/misses and the collector's dead evictions.

Adapted to this branch: the collector here downloads inline (no batched
EvictedDownload path), so the dead-data check sits in the inline path
and the batched-wait slow-frame logging of the original is not taken.
The EDEN_NX2_SLOW_FRAME_MS diagnostic commit of that PR is not included.

The author measured this on an RTX 3050 Laptop 4 GB under Linux; it is
not measured on the Legion (RTX 2060 6 GB, Windows) yet. Our ec8e93f
profile shows 0 buffer downloads, so the download half may stay quiet;
the transcode half targets host CPU time. No FPS claim.

Tests: range_cover and transcode_cache (from the original PR) linked
standalone against Catch2 and pass; touched texture cache / Vulkan
translation units compile with GCC 14.

Co-authored-by: JavocSoft <javocsoft@gmail.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
…in, EDEN_NX2_BACK_MACROS=1)

The ec8e93f Legion profile still spends ~102 ms/s of the split's
front in macro DMA reads. Ocarina of Time's render enable macro reads
report words with MME_DMA_READ_FIFOED; on the front each read waits for
the queued work before it (the 88f1b3d fast path only helps when the
report words have already landed).

A static pass over the macro code (TuringMacroInterpreter::
AnalyzeEffects) finds the methods a call can write and whether it uses
the data RAM. A macro that reads memory into its FIFO, uses no data RAM
and writes only registers the front can do without (BackWritable: not
macro code/bindings, shadow RAM mode, draws, constant buffer data,
reports, uploads, syncpoints, MME_DMA_READ) is sent to the back as one
entry with its parameters (GpuSplit::DeferWords) and runs there with a
macro engine of its own, reading the words as a drained front would.
The registers it can write are the back's until the front writes them
(FrontTakeOver) or needs them (FrontRegister, "back register" drain
site); a macro code change waits for queued back calls ("back code").
If the back sees a write outside the predicted set, that macro runs on
the front from then on and the front resynchronises (FrontVerify).

Interaction with this branch: macros that stay on the front still use
the 88f1b3d occlusion-report fast path (FrontDmaReadQueued), which now
reads the data RAM address through FrontRegister. Back macros never
write report or semaphore registers, so the 3863d03 acquire path and
its queued-release tracking are unchanged. FrontMacroCountsDraw from the
Fritchii port does not exist in this tree and is not added.

Off by default: set EDEN_NX2_BACK_MACROS=1 to test it. Not measured on
the Legion; the Fritchii author saw front MME DMA wait 218 -> 0 ms/s but
no FPS change on an RX 6900 XT. No FPS claim.

Ported from Frizti#24 (mkooner; commit 2f82fd2, itself a port of
zortos293#805 a442c38 by jpkto), the BACK_MACROS piece only:
Frizti#24
zortos293#805

Tests: turing_macro_effects (from the original) linked standalone
against Catch2 and passes (6785 assertions, incl. random code checked
against the interpreter). maxwell_3d.cpp, macro.cpp, gpu_split.cpp and
maxwell_fast_method.cpp compile with GCC 14; the split end-to-end test
needs the full tests binary and was not run here.

Co-authored-by: jpkto <255363096+jpkto@users.noreply.github.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
…included

Ported from zortos293#869 (commit 4e3142e by Xiphias1337):
zortos293#869

Ocarina of Time (0400486024DEC000) streams its data through a
statically linked zstd 1.4 while it plays (about 130-160 MB/s in Kokiri
Forest per the author); the JIT-compiled sequence decoder took about a
quarter of the two busiest guest cores. Our ec8e93f Legion profile
still shows host_cpu_busy ~336 ms/s.

The loader patches the first instruction of ZSTD_decompressMultiFrame,
ZSTD_decompressBlock_internal and ZSTD_decompressContinue in main (build
E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348, each checked against its
original code) with an SVC Horizon does not have; Svc::Call hands it to
Core::OotZstd::Handle, which decodes with the host's libzstd (a twin
ZSTD_DCtx per guest dctx for the streamed blocks). Calls the host's zstd
refuses run the game's code as before. The Legion's eden-log for the
ec8e93f session shows this exact main build ID, so the hook should
engage there; the log lines "Ocarina zstd: ..." confirm it.

Added here: EDEN_NX2_OOT_HOST_ZSTD=0 leaves the game's decoder alone,
for A/B and as a fallback if anything looks wrong.

Not measured on the Legion. The author's 36 -> 56 fps (RX 7900 XTX +
9800X3D, 1.5x, stacked with zortos293#717 pieces) does not transfer; no FPS
claim. The image should be unchanged (decoded bytes are identical).

Tests: none upstream; oot_zstd_hook.cpp, svc.cpp and nso.cpp compile
with GCC 14.

Co-authored-by: Xiphias <12048705+Xiphias1337@users.noreply.github.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Ported from Spikol02/Nyamu#15 (commits 1ffea4b47e1d and 546c16dbb347):
Spikol02/Nyamu#15

UsageTracker::Track is called on every storage buffer bind. This tree
already skips the last few identical ranges and keeps one run of pages
known to be full ([full_begin, full_end)); anything outside that run was
rewritten page by page. Draws bind several disjoint multi-megabyte
storage buffers between resets, so only one of them got the fast path.

Now one bit per 4 KiB page records that the page is all ones, and pages
outside the run are filled 64 at a time only where a bit is missing.
Reset clears the bits with the pages. The existing run and the
Nx2Perf verify path are kept. EDEN_NX2_USAGE_TRACKER_SKIP_FULL=0 writes
every page again, as before.

The author measured back "bind stage buffers" 137-165 -> 81-96 ms/s on
an RX 9070 XT + 7800X3D (Castle Town). Not measured on the Legion; this
is back-thread CPU, not the front writer wait. No FPS claim. No visual
change expected: what IsUsed reports is unchanged.

Tests: usage_tracker.cpp gains the original's randomized comparison
against the old tracker and the reset case; all 7 cases pass standalone
(with and without EDEN_NX2_USAGE_TRACKER_SKIP_FULL=0).

Co-authored-by: Spiks <42014907+Spikol02@users.noreply.github.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Ported from Spikol02/Nyamu#21 (commit 5667042cb0a8), itself from
Frizti/Fritchii 82a57c3 by Frizti; the cost was found in
zortos293#101 (Kradnah's OoT frame dump issue, measured by
d3zessete):
Spikol02/Nyamu#21

Not already in this tree: a native Turing graphics program's storage
pointer was still bound as a bare pointer, up to 8 MiB of its mapping,
which every draw then synchronizes and tracks. Now the word after the
pointer is taken as the size, as Fritchii ships it. Table pointers keep
their own sizing. EDEN_NX2_GRAPHICS_SSBO_BARE=1 binds the mapping again.

Reported upstream as about 9% in OoT; not measured on the Legion here.
No FPS claim.

Visual risk: if a shader reads past the size word the binding is short
and the read returns zero; upstream reports vegetation looked the same.
A/B with EDEN_NX2_GRAPHICS_SSBO_BARE=1 if anything goes missing.

Build check: vk_buffer_cache_base.cpp compiles.

Co-authored-by: Spiks <42014907+Spikol02@users.noreply.github.com>
Co-authored-by: Frizti <FriztiMouche@gmail.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
The kernel-lock part of javocsoft#16, commit e0ae6b0 by
JavocSoft, itself from zortos293#717 by Stephen Jayakar
(stephenjayakar). Only this commit: Occlusion Off (ed2b38a) and
the rest of zortos293#16 are not taken.
javocsoft#16

- KSpinLock spins for up to 20 us instead of 128 pauses before it sleeps.
- KAddressArbiter watches the word for up to 8 us before a thread sleeps in WaitIfLessThan or
  WaitIfEqual. If the word stops satisfying the wait, it ends with ResultInvalidState, which is
  what the kernel returns to a thread that entered the SVC that much later. A thread whose
  spins seldom pay off stops spinning.
- PhysicalCore guards its context with a KSpinLock, and Idle waits on the interrupt flag itself
  instead of a condition variable. Its 100 us idle poll pauses instead of yielding.
- KThread's context guard, which the scheduler only try_locks, is an atomic flag.
- The current thread and host thread ID are read from constinit thread_locals.
- GetParentReference computes each member offset once.
- SpinRecursiveMutex::try_lock reads before its compare-exchange.
- Memory traps on GPU-cached pages reuse the host pointer the access already looked up, keep the
  last four clean read areas per core instead of one, and remember a core's last host page to
  device page translation until the device mappings change (DeviceMemoryManager::
  MappingGeneration). Threads that are not guest cores get their own dirty memory slot instead of
  sharing, and locking, the last core's.

Applied as is except src/tests/CMakeLists.txt, where kernel_locks.cpp
was added by hand next to the other core tests.

Upstream measured Kokiri Forest on an RTX 3050 Laptop with Occlusion
Off, 27.9 -> 29.4 fps over two runs. Not measured on the Legion here,
and this tree runs without Occlusion Off; no FPS claim.

Visual risk: none expected; this is CPU-side scheduling and memory
trap bookkeeping. Behaviour risk: KAddressArbiter's spin can end a wait
with ResultInvalidState when the word changes while spinning, which is
what a late SVC entry would see; watch for hangs or stutter in titles
that wait on arbiter words.

Tests: new kernel_locks.cpp, 2 cases pass standalone. Touched core TUs
and k_scheduler/k_thread/svc/k_condition_variable compile.

Co-authored-by: JavocSoft <javocsoft@gmail.com>
Co-authored-by: Stephen Jayakar <aj12ay@gmail.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Visual commit for the OoT canopy haze, kept separate. Ported from
zortos293#825 by pvns1108 (commits db2a16d and
cd9e71e), fixing zortos293#745 reported by justin-wq1337:
zortos293#825
zortos293#825 was taken over the overlapping zortos293#811 (janvitos) because it also
fixes Select/LowerFp16ToFp32 for SelectF16.

- FFMA.FMZ.SAT and HFMA2.FMZ.SAT skipped the FMZ select when SAT was
  set, so 0 * inf + c became sat(NaN) = 0 instead of sat(c). The select
  now always runs, then SAT. FMUL/HMUL2 keep the SAT shortcut.
- HFMA2.FMZ and HMUL2.FMZ compared F16 operands with an F32 zero; the
  zero now takes the operands' type.
- IREmitter::Select builds SelectF16, and LowerFp16ToFp32 maps
  SelectF16 to SelectF32 for hosts without float16.

Applied as is except src/tests/CMakeLists.txt (line added by hand).

Checked: EDEN_NX2_OOT_VISUAL_WORKAROUND is not in this tree. It lives in
sylvieistaken/selevet-eglantine as an opt-in (=1) per-shader edit of the
OoT fog integration and log-bloom exposure, marked temporary and
unvalidated, and it also thins interior fog. Not ported.

Visual risk: changes shader output wherever a title uses FFMA/HFMA2
with FMZ+SAT (expected to restore the addend, e.g. haze/fog terms) and
any shader cache built before this will recompile. Not checked on the
Legion yet; needs an outdoor A/B in Kokiri Forest. No FPS claim.

Tests: new maxwell_fmz.cpp, 4 cases pass standalone; 3 of them fail
against the old translation.

Co-authored-by: pvns1108 <pvns1108@gmail.com>
Co-authored-by: xTektor <xTektor@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

shader: FFMA.FMZ.SAT and HFMA2.FMZ.SAT drop the addend when a multiplicand is 0 and the other Inf/NaN