Skip to content

Build F: newest Lichii (with Ocarina DLSS) + build D (shader cache change + upstream PR 345) - #6

Merged
Ihitko merged 21 commits into
oot-dlss-345from
oot-shader-345
Nov 7, 2026
Merged

Ihitko merged 21 commits into
oot-dlss-345from
oot-shader-345

Conversation

@Ihitko

@Ihitko Ihitko commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Build F for Ocarina of Time testing: Lichii's newest nx2 (b7ff181, which includes upstream PR 606, NVIDIA DLSS for Ocarina of Time) plus build D (branch oot-shader-345): build C's shader cache change (a49a1a3) and upstream PR 345 (head 09f3c7a).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fp7Vt3eYkQsGicwZZK3hFa

celmax85 and others added 21 commits November 6, 2026 20:52
… front

The split's front drained the back before every native macro DMA read and
every semaphore acquire. Games read through MME DMA often (Ocarina of Time,
where this was measured: ~20,000 reads a second, ~48% of the front spent
draining), so the two GPU threads kept waiting on each other. Applies to
every Switch 2 title running on the split.

- gpu_split: track queued writes per 64-byte block, plus a barrier position
  for work whose writes are not tracked by address. A front read waits only
  until the back has run the last queued work that may write its words.
- dma_pusher: classify writers (compute and copy launches, as before) and
  barriers (host methods and other engines that write memory untracked);
  command pages already fetched as CPU-written skip the writer wait until
  the GPU writes them.
- maxwell_3d: inline-to-memory uploads, report semaphores and inline constant
  buffer data note the ranges they write; reads of inline constant buffer
  words still queued are answered from the front's copy of them. Every
  4096th read still drains, and a read of GPU-written memory turns the
  precise reads off.
- puller: skip the back drain for greater-or-equal acquires already met in
  memory and for acquires the channel's own queued release meets; otherwise
  wait only for writers of the semaphore. Addresses queued GPU work met 8
  times in a row are trusted, and the back checks each skipped acquire and
  stops trusting an address it finds unmet.
- gpu_thread: a GPU release that latches a suspended acquire wakes the GPU
  thread at once instead of after its 1 ms poll.
- EDEN_NX2_MME_DMA_STATS: why reads and acquires waited, barrier sources and
  GPU thread time spent with channels suspended.

Measured in Ocarina of Time (Kokiri Forest, RTX 4060 Ti, Ryzen 9 5950X):
~35.5 -> ~39.3 fps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…are in place

Native programs read a 64 KiB uniform bank with indirect offsets, and writes
to it can go unnoticed by the memory tracker. nx2 uploaded such a bank again
once a frame; games that rewrite per-draw data in it between draws then
draw with stale words for a frame now and then (white flashes in Ocarina of
Time, where this was measured, Eglantine 031e074).

- Check the bank on every bind, comparing each of its pages with a snapshot
  of its last contents, and mark only the pages that changed as CPU-modified
  so the normal upload path refreshes them. Unchanged pages keep their
  tracking (marking costs two mprotect calls per page).
- Small uniform buffers within one device page are compared in place
  instead of being copied to the scratch first; the slot's copy is what gets
  uploaded, so a later reuse finds exactly the uploaded bytes.
- InvalidateGPUCache gathers the CPU-written ranges first so the rasterizer
  takes each cache's lock once instead of once per range.
- EDEN_NX2_MME_DMA_STATS: checked bank pages (same, new, changed, and
  whether the tracker knew of the change) and page protection counts.

The tests now expect page-granular marks instead of a direct upload.

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

Builds on Lichii zortos293#215 by WeslieDE (which includes zortos293#204 by vouion): below
High GPU accuracy the query cache holds report and semaphore writes until a
fence, and a fence runs its callback at once, so macro DMA reads of report
memory saw stale occlusion counts (white flashes in Ocarina of Time, where
this was found). FlushQueryResults writes the region's reports out and waits
for an operation queued behind the pending report writes. Without the split
the macro engine flushes this way before its DMA reads.

On the split's precise front (previous commits), the flush runs on the back
only for reads of pages report_semaphore writes went to, and not for reads
answered from queued inline constant buffer words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the split front

In ShadowRamControl::Replay the back writes the shadow RAM values, not the method arguments,
so the front's cb_shadow copy of those words was wrong. Note such writes as opaque and keep
them out of cb_shadow: macro DMA reads of them wait for the back. Covers single and multi
method writes.

Reported by CodeRabbit on zortos293#345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A command fetch from a page first fetched as a CPU-written list skipped the wait for queued
compute and copy work. Nothing guarantees that such work can't write that page before the
back runs it, so the front could parse stale words (Ocarina rewrites 28 such pages while
loading). Command fetches wait for queued writers again, as on nx2. This costs ~2-3 ms per
frame in Kokiri Forest.

Reported by CodeRabbit on zortos293#345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…PU-written DMA sources

From CodeRabbit's review of zortos293#345:
- Queued writes and report pages are keyed by device address, so a write and a macro DMA
  read through different GPU addresses of the same memory meet.
- Past 4096 report pages, every macro DMA read flushes pending query reports instead of
  missing the new pages.
- A precise macro DMA read of pages the GPU has written lets the back check the buffer cache
  and download newer data first; waiting for the back alone left it in a host buffer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te can change the value

The greater-or-equal fast paths (SEM_ACQUIRE and SEM_EXECUTE) assumed releases only raise the
value. A queued release can lower it, and GPU-written data may still be in the buffer cache, so
they now also require that no tracked write to the semaphore is queued and that its page is not
GPU-written. Reported by CodeRabbit on zortos293#345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WaitForWriters returns false when the writers have already finished, which skipped the recheck:
a writer that marked the pages after the first check left the fetch on an old submitted
snapshot. Reported by CodeRabbit on zortos293#345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew of zortos293#345)

- Drop learned acquires: an acquire that queued work may meet now waits for it.
  Skipping it on past successes let the commands after an unmet acquire run.
- StableSemaphore also waits for queued compute and copy work.
- A channel's own queued release meets an acquire only if no later write to the
  word is queued, and not at High GPU accuracy (releases land at fence time).
- Key the constant buffer shadow by device address, like the write tracking,
  and keep only the words ProcessCBMultiData writes within the buffer size.
- Track report pages globally: the front marks the 3D reports it queues, the
  back marks every report left pending in the query cache (any engine or
  channel), instead of a per-channel list capped at 4096 pages.
- Bound the FlushQueryResults wait to 2 s with state the callback shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps nx2's OnCacheInvalidations (it claims CPU writes first, zortos293#397) in place of this
branch's batched version, and the bounded query flush wait of cc808c1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MSVC 14.51 treats C4456/C4457/C4458 as errors: the stats loop in
Maxwell3D's macro DMA read reused 'source' and 'count', and Query's
'device' hid RasterizerVulkan::device. No behaviour change.

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

Report pages stayed marked for the whole session, so nearly every read of
them drained the back to flush the query cache: madebyjordan counted only
540 of 2.08 million such reads with a pending result in the words read.

The query cache now counts, per 16-byte slot hash, the reports it registers
and the results it writes out, as in Lichii zortos293#243 by tibzejoker. The front
flushes only when the counts differ for the words read. The wait before it
has already run the queued reports the read may see, so the back has
registered them. The page bitmap is gone. EDEN_NX2_MME_DMA_STATS also
reports how many reads flushed.

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

Each channel kept its own copy, so a word uploaded by one channel and then by
another could be served with the first value. The copy now lives in GpuSplit,
keyed by device address, and the last upload of a word wins whichever channel
sent it. Reported by CodeRabbit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GCC 13.3 at -O3 unswitches the per-word loop of FrontCallMultiMethod on the
switch that zortos293#178 inlined into it. The loop that the other methods take then
has no case for the macro upload methods: load_mme.instruction never reaches
AddCode, the front's macro RAM stays empty, and every Turing macro fails with
"instruction outside uploaded RAM" (black screen at boot in Ocarina of Time).
In the object file the function has no AddCode or ClearCode call left; with
the helper kept out of line both are back. MSVC and clang builds are not
affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s branch boot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aphores on the split front (review of zortos293#345)

- A draw that writes a storage or texture buffer makes the front count the
  draws before the next wait for idle as writers, so macro DMA reads after
  it wait for them. Nothing changes while no draw writes memory.
- Each split stop starts a new epoch; a channel's queued releases from an
  earlier one are dropped instead of meeting acquires.
- An acquire met after its wait is accepted only if its page is not
  GPU-written; otherwise the drain path flushes the word first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the split front

The flag that enabled this was set by the back when it bound a written storage or
texture buffer, so the front could pass the wait for idle after the first writing
draw before the back had seen it, and a following macro DMA read could miss its
writes (CodeRabbit on zortos293#345). Track draws unconditionally instead, from draw.begin:
in an instance loop the front skips draw.end and the back defers the draw until the
wait for idle flushes it.

Tests cover a plain and a deferred instanced draw before a wait for idle, host syncs,
and a macro read position waiting for a stalled back. Ocarina of Time in Kokiri
village: macro DMA reads that wait for writers stay at the 313 from boot, fps
unchanged.

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

- EvictUnusedPipelines only evicts while more than 8,000 graphics pipelines are
  cached, and then the longest unused ones a draw has used, down to 7/8 of the
  limit (EDEN_NX2_PIPELINE_EVICT_ABOVE; 0 restores the old behavior). Prebuilt
  pipelines no draw has used keep zortos293#550's budget. Ocarina of Time evicted 6,182
  pipelines in a 37 min session (~1,000-2,400 cached at a time) and draws
  waited 230 s in all for pipelines being built again.
- The first boot copies the cache version 53 records beside this version's
  (vulkan_import, loaded after it) and merges the v53 used-pipeline list, so
  pipelines a v53 build used are built at boot again. The records are the same
  format: zortos293#432 bumped the version only for the SPIR-V it emits, and every record
  is translated again when it loads. The v53 files are left as they are.

Skipping draws on unbuilt pipelines is left to zortos293#465's native_async_draws setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTxNSeWfimx1VbMuL9DgoN
Test build D: merge the oot-split-front-and-flashes branch into oot-shader-345
@Ihitko
Ihitko merged commit e99f34f into oot-dlss-345 Nov 7, 2026
9 checks passed
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.

3 participants