Repository navigation
Ocarina of Time on RDNA 4 and Zen 5: 224 fps VRR cap, AMD FSR 4 and frame generation, pacing, loads, HDR, 21:9 (draft, tracking) - #831
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
|
Must be merged quick |
|
@zortos293 is it OK if Lichii reads gamepads through Microsoft's GameInput API? It delivers each new controller reading through a callback, so input no longer waits for SDL's 1 ms pump. Microsoft's GameInput header (API v3) is MIT-licensed, so it can go in-tree with its notice. The runtime is Microsoft's separately installed redistributable: Lichii wouldn't ship it, only load it when it's present, and keep SDL everywhere else. It's one commit in this PR, easy to drop if you'd rather stay with SDL only. |
b5489a2 to
a8017af
Compare
…g slot Trapped writes from threads that are not guest cores used guest core 5's slot (NUM_CPU_CORES - 1), so core 5 took sys_core_guard on every trapped write and its write path could share a scratch buffer with them. Those threads now get their own slot after the cores' (one more GPUDirtyMemoryManager in System, one more write area and scratch buffer in Memory), and only that slot takes the guard. The PR's EDEN_NX2_SYS_SLOT kill switch (default on) is gone. The PR gives only the aggregate gain of its kernel group, no number for this change alone. zortos293#717 also adds a further dirty memory manager for its buffer cache write handoff; that belongs with the buffer cache changes. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read and write traps on GPU-cached pages now reuse the host pointer the trapping access already looked up instead of walking the page table again. A core's trapped writes remember the device page of their last host page while the device mappings stay the same (the new DeviceMemoryManager::MappingGeneration), so writes to the page the core already collects into skip the device address lookup. A core keeps its last four clean or flushed read areas instead of one. The PR's EDEN_NX2_TRAP_FASTPATH kill switch (default on) is gone. The PR gives only the aggregate gain of its kernel group, no number for this change alone. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetParentReference computed the member offset (OffsetOfCalculator) on every call, and the kernel's intrusive trees call it for each node they visit, under the scheduler lock. At run time the offset is now computed once per member and kept in a function-local static; constant evaluation still uses the calculator. The PR's EDEN_NX2_OFFSETOF_CACHE kill switch (default on) is gone, and with it the case where the switch was read before its dynamic initialization. The PR gives only the aggregate gain of its kernel group, no number for this change alone. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GPU caches' SpinRecursiveMutex slept on std::atomic::wait once its spin ran out. On Windows that goes through a winpthreads condition variable: the unlocking GPU thread blocked on a shared mutex and woke every sleeper of the slot. Sleepers now use Common::Nx2AddressWait (WaitOnAddress / WakeByAddressSingle on Windows), and try_lock reads the state before trying the compare-exchange. The PR's EDEN_NX2_CACHE_LOCK_WAIT kill switch (default on) is gone. The PR gives only the aggregate gain, no number for this change alone. zortos293#717's owner hook in this class (SetOwnerHook, for EDEN_NX2_WRITE_HANDOFF) belongs with the buffer cache write handoff and is not part of this commit. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x86-64 x86-64 is TSO, so LDAPR/LDAR/LDAXR need no locked read-modify-write and a DMB that only orders loads (xxLD) or only stores (xxST) orders nothing the host does not already order. Ordered stores keep XCHG, so STLR followed by LDAR stays ordered. Ocarina of Time has ~68k LDAPRB sites (static init guards), each a LOCK XADD that bounced the guard's cache line between the cores. Two new UserConfig options (tso_ordered_loads, elide_ld_st_barriers), set by ArmDynarmic64 for every CPU accuracy but Paranoid. The PR's EDEN_NX2_TSO_LOADS kill switch is dropped. Claimed effect: part of zortos293#717's JIT work; the PR gives only the aggregate (Kokiri Forest 1440p, Ryzen 7 3700X + RTX 3080: 34.8 -> 50.9 fps for the whole PR). Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JIT spilled ~9% of its instructions to stack slots in Ocarina of Time's hot code while callee-saved GPRs sat empty. With spill_to_free_gpr an evicted GPR value goes to a free callee-saved GPR (it survives host calls) before a stack slot; with scratch_copy_to_free_reg an operand that an instruction overwrites but that is still needed is copied to a free register instead of evicting the original. Both are new UserConfig options, set by ArmDynarmic64 for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_SPILL_GPR and EDEN_NX2_JIT_SCRATCH_COPY kill switches are dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each A64 block saved %rbp, pointed it at its stack layout and restored it: three instructions per block that nothing in the JIT reads and that Windows unwinding does not use. The new omit_block_frame UserConfig option leaves them out; ArmDynarmic64 sets it for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_NO_FRAME kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The A64 fast dispatch table was a fixed direct-mapped array of 2^20 entries, 16 MiB per guest core. fast_dispatch_table_bits sizes it at run time and fast_dispatch_set_associative makes it 4-way set associative (the four ways share one 64-byte cache line; a miss inserts at way 0 and drops the oldest). ArmDynarmic64 uses 2^16 entries, 4-way: 1 MiB per core. The PR's EDEN_NX2_JIT_DISPATCH switch (sizes 8-24 bits or the old table) is dropped; its default is what ships. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A64JitState's return stack buffer grows from 8 to 32 entries and the new rsb_size UserConfig option picks how many are used (power of two, 8 to 32; the default stays 8), so returns from deeper call chains skip the dispatcher. ArmDynarmic64 uses 32 for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_RSB32 kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unlinked A64 block exit was an inline PC store plus return (22/23-byte patch sites, and an inline exit after the halt check). With the new compact_block_exits UserConfig option each target gets one shared exit stub, created while the first block that links to it is emitted and kept until ClearCache; patch sites become 5/6-byte rel32 jumps to the target block or to its stub. ArmDynarmic64 sets it for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_COMPACT_EXITS kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every linked A64 block exit compared halt_reason before jumping on. A link to a higher guest PC cannot close a loop on its own: every cycle of blocks contains a link to the same or a lower start PC, or an indirect branch. With the new halt_check_on_back_edges UserConfig option (without cycle counting only) forward links jump straight on, and links to the same or a lower PC and the RSB/fast-dispatch handlers check for a halt instead, so every loop still sees a halt request. ArmDynarmic64 sets it when it counts no cycles (multicore). The PR's EDEN_NX2_JIT_BACKEDGE_HALT kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Memory fault and abort paths, atomic fallbacks and unlinked-exit stubs were emitted right after each A64 block, between the hot blocks. With the new far_code UserConfig option the last quarter of the code cache holds that cold code (EmitContext::far_deferred_emits, ColdEmits()), so hot blocks sit densely. On Windows the far region has its own commit front, with the same commit-and-retry loop as the main region (zortos293#310). It does nothing where the code space needs W^X switching. ArmDynarmic64 sets it for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_FAR_CODE kill switch is dropped. The commit and far-code state is declared before BlockOfCode::constant_pool: the ConstantPool constructor already calls EnsureMemoryCommitted, which reads it (zortos293#641's a8b9923: with the old order the near region could be left uncommitted and JIT creation crashed in Xbyak::CodeArray::db). Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing op Every conditional branch, CSEL and CBZ/TBZ reloaded NZCV from the guest state and rebuilt the host flags from it, even right after the Add/Sub that had just set them. With the new reuse_host_flags UserConfig option (without cycle counting only) the emitters track while the host flags still equal the stored NZCV (BlockOfCode::nzcv_live_pos, kept across the movs of SetW/SetX and the %rbp restore) and conditions use them directly; a block's last SetCheckBit leaves the bit in ZF for its CheckBit terminal. The guest NZCV is still written. ArmDynarmic64 sets it when it counts no cycles. The PR's EDEN_NX2_JIT_FLAGS kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetW and ZeroExtendWordToLong always emitted `mov r32, r32`, also for values a 32-bit instruction had just written (GetW, ReadMemory32, LeastSignificantWord, Add32, Sub32, ConditionalSelect32), whose host register is already zero-extended. With the new elide_redundant_zext UserConfig option those are used as they are. Adds Argument::GetInst (zortos293#641's d5b8fe7). ArmDynarmic64 sets it for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_ZEXT kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ldr/str xN, [xM, #imm]` computed the address with a separate LEA (and often a register copy) before the fastmem access. With the new fold_address_offsets UserConfig option (unchecked 64-bit fastmem only, where the sum wraps exactly like the guest's), an Add64(base, imm) whose only use is one fastmem load or store is not emitted and the access adds the displacement in its addressing mode. Fault paths add the displacement before the fallback and take it off again on return; callback and page-table paths compute the full address. ArmDynarmic64 sets it for every CPU accuracy but Paranoid. The PR's EDEN_NX2_JIT_FOLD_ADDR kill switch is dropped. Claimed effect: the PR gives only the aggregate for its JIT work. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LDADD and the other LSE read-modify-writes, SWP and CAS were translated as an exclusive
load/store pair that ends the block. With the new native_lse_atomics UserConfig option
(fastmem exclusives with Unsafe_IgnoreGlobalMonitor only, x64 backend only) they become
new A64AtomicRmwMemory/A64AtomicCasMemory IR ops: LOCK XADD for LDADD, XCHG for SWP,
LOCK CMPXCHG for CAS and a CMPXCHG loop for the others, with a callback CAS-loop fallback
for fastmem faults and non-fastmem accesses (fallback stubs go to the far region). The
local monitor is still cleared, as the exclusive emulation did. ArmDynarmic64 sets it
where those conditions hold (every CPU accuracy but Paranoid). The PR's
EDEN_NX2_JIT_ATOMICS kill switch is dropped.
The callback path (no fastmem, or a fastmem fault: GPU-cached pages are protected in the
fastmem arena) is a loop of MemoryRead and MemoryWriteExclusive, which is a compare-and-swap
against the value read. A refused write is retried from a new read; it never counts as done
because a second read shows the old value again. The PR returned there ("unmapped or aborted
access"), but the word can change and change back between the refused write and that read: a
mutex another core locked and unlocked while the callbacks handled the GPU-cached page. The
CAS then reported success without writing, the guest thread went on owning a lock it never
took, and nn::os aborted at the unlock on finding another thread's handle in the word
(UnlockSlimMutex -> LeaveInternalCriticalSectionNoInline -> diag::Abort, a userspace panic
about 10 s into one of three Ocarina of Time boots). LDADD and the other read-modify-writes
lost updates the same way. Retrying stops only at a memory abort the frontend raised for the
access (UserConfig::memory_abort_halt_reasons, Lichii's DataAbort and PrefetchAbort), whose
abort check leaves the block before the result is used. The fallbacks live in
atomic_fallback.h; tests/core/dynarmic_atomic_fallback.cpp scripts the other core and fails
with the PR's loop (lock taken without the write, an addition lost).
Claimed effect: the PR gives only the aggregate for its JIT work.
Ported from zortos293#717 by `stephenjayakar`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A guest YIELD or WFE is a spin-wait hint; ExceptionRaised only logged it at trace level and returned, so the host thread spun on at full speed. It now pauses twice (YIELD on an arm64 host), giving the SMT sibling, often a GPU thread, the core for a moment, as GetCNTPCT already does for counter-polling loops. The PR's EDEN_NX2_HINT_PAUSE kill switch is dropped. Claimed effect: the PR gives only the aggregate (its kernel/JIT spin work as a whole). Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ocarina of Time creates a Z-prepass occlusion query and a samples-passed query per object and feeds them to conditional renders (macro 0xe2c), which make the split GPU front read query results back. The built-in patch "Occlusion Off" (cLohan's community OcclusionOff_ZPrePassAndSamplesQuery, module offsets = the IPS offsets - 0x100) turns both render settings off where they are mirrored (`ldrb w8, [x0]` -> `mov w8, wzr`, six sites, original bytes checked), so the game issues neither. Like the other built-in patches it applies only to main build E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348 of 0400486024DEC000 and can be turned off in Add-Ons; the PR's EDEN_NX2_OOT_OCCLUSION_OFF switch is dropped. Claimed effect: the PR gives only the aggregate (Kokiri Forest 1440p, 3700X + RTX 3080: 34.8 -> 50.9 fps for the whole PR); frame sheets matched build 116 there. Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dictionaries Ocarina of Time's asset streaming jobs decompress with the guest's ZSTD_decompressMultiFrame (main+0xEB380); each of its three callers re-initialises a static default DCtx right before the call and only uses the return value. The built-in patch "Native zstd" turns those three bl into svc 0xF0-0xF2 (original bytes checked), and svc_nx2_zstd.cpp decompresses on the host with libzstd: zstd1 and skippable frames only, complete, with the guest's ZSTD_DDict copied into a host DDict of the same type and dictID (cached, checked at its edges per call and in full once a second). Anything it does not reproduce exactly (raw dict arguments, odd frames, unusual dictionaries, errors) runs the game's own function, as the bl would have. The SVCs are handled only for program 0400486024DEC000 and the patch only applies to main build E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348; it can be turned off in Add-Ons. The PR's EDEN_NX2_OOT_NATIVE_ZSTD (patch) and EDEN_NX2_OOT_ZSTD_DICT (dictionary calls) switches are dropped, both on. The per-second counters line logs at debug level. Claimed effect: the PR gives only the aggregate (wave 4 of zortos293#641, 39.4 -> 45.5 fps with the other wave-4 changes). Ported from zortos293#717 by `stephenjayakar`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vertex input A/B Keeps dynamic vertex input on AMD's driver, as before. The first session on each side builds its pipelines again (the key differs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ized relink A/B Keeps AMD's fast-linked pipelines as they are, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…et and the indirect-branch cache For the integration A/B only: hcr = UserConfig::host_call_return, ibc = UserConfig::indirect_branch_cache. Drop before the PR; the winner ships unconditionally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…IVE_UNIFORMS=0 Turn storage through the GPU page table and constant buffers by device address off, each on its own, for the A/B of the test campaign. The winning side ships unconditionally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…descriptor_heap Nothing binds a descriptor heap yet (the heap object and heap-mode pipelines are the next steps of the bindless texture path), so the device does not ask for the extension unless EDEN_NX2_DESCRIPTOR_HEAP=1. Dropped once heap pipelines use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t code Extends the EDEN_JIT_OFF switch for the integration A/B. Drop before the PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and per changed state For the A/B of the per-draw dynamic-state packet against the recording it replaced (each changed state its own scheduler command, the same value compare). The winning side ships unconditionally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iour changes - EDEN_NX2_EXP_HEAVY_DISPATCH=1: run the AA network's heavy dispatch while the output pass is replaced. - EDEN_NX2_EXP_PRESENT_FILTERS=1: keep FXAA/SMAA and the scaling filter on the DLSS/FSR image. - EDEN_NX2_EXP_START_RATE=console: the display starts frames no faster than the console's vsync rate even with the frame rate unlocked. - EDEN_NX2_EXP_FG_HUDLESS=0: AMD frame generation gets no HUD-less image. FSR 4 and frame generation follow their settings (oot_anti_aliasing, frame_gen, frame_gen_type), Anti-Lag 2 the anti_lag setting, display-driven vsync EDEN_NX2_HOST_VSYNC=0, and the FSR 4 version pin EDEN_FFX_UPSCALER. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… objects Graphics pipelines stay pipelines unless EDEN_NX2_SHADER_OBJECT=1, for the pipelines vs VK_EXT_shader_object A/B on AMD. The winner ships unconditionally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s out for the sweep A built-in patch record can name an environment variable that, set to 0, leaves it out: - EDEN_OOT_UW_SIZE_TABLE=0: the size table, index and cap records. A/B whether Hookshot's picture and scene sites alone make the 3440x1440 frame. - EDEN_OOT_UW_MARKERS=0: the two extra world-to-HUD marker sites, leaving Ivan0xFF's bubble site only. - EDEN_OOT_UW_HUD_EDGES=0: the RomFS layer leaves the UI layouts alone, so the HUD stays centred 16:9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…server's switch command
The A/B switches of the OoT work are getenv reads cached in statics at first use, so every A/B
side needs its own launch (~35 s of boot and save load each). Common::Nx2Switches keeps them in
one registry instead: each switch starts from its environment variable, parsed exactly as its
read site parsed it (value[0] == '1', == "0", == "all", set at all, or the guest-CPU name list),
so a launch with the variable set behaves as before; a read is one relaxed atomic load.
The debug server's `switch` command {name, value} (null unsets) requests a value and waits until
it took effect; Latch applies requested values once a frame and logs
"switch NAME = VALUE (from -> to) at frame N". Without a name it lists the switches with their
value, state and launch state. It takes no tool lock (the registry is thread-safe), so status
polls continue while it waits. GuestCpuOff's cntpct and hostfn change emitted code and keep the
launch value (AtLaunch).
Tests: the parse rules, name lookup, request-then-latch, the launch value, and the request
parser of the command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cheap switches read Common::Nx2Switches instead of a static set at first use, so the debug server's `switch` command flips them in a running game; the rasterizer's TickFrame latches requested values at the frame boundary, so no flip lands inside per-frame work (an upload batch, staged dynamic state). Each site was checked for state kept across frames: - zortos293#805: EDEN_NX2_EXP_NO_PARK (per wait), NO_FENCE_QUIET (per fence release), FENCE_FLUSH_TWICE (per fence), COLD_ALWAYS (per hint), NO_EARLY_READ (per dispatch), NO_UPLOAD_BATCH (per upload; the frame-boundary latch keeps a batch from being split) - EDEN_NX2_GUEST_CPU_OFF spin, idle, arb, svcwait (per call); callers pass the bit, not the name, so the spin hint path does no string search; cntpct and hostfn stay launch-only - EDEN_NX2_EXP_DISPATCH_BARRIERS=all (per dispatch), EDEN_NX2_DYNSTATE_BATCH=0 (per draw; staged state is recorded within the draw), EDEN_NX2_PRESENT_CADENCE=0 (per present-thread loop), EDEN_NX2_TC_SKIP_STALE=0 (per image upload) - FSR: EDEN_NX2_EXP_FG_HUDLESS=0 (per frame; hudless_ready resets), EXP_HEAVY_DISPATCH=1 (per dispatch), EXP_PRESENT_FILTERS=1 (per present) - pipeline builds: EDEN_NX2_EXP_NO_CLAIM (per held draw), NO_BUILDER_BOOST (gates raising only; lowering follows the raised flag, so a flip during a build cannot strand a thread high), EVICT_9000 (per eviction pass), NO_GPL_RELINK (per link) EDEN_INPUT_IMMEDIATE stays a launch switch: its SDL half decides at init whether to create the GameInput source, so a runtime flip would A/B only half of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ame's queue Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elines off descriptor buffers Buffer device addresses now report true on Vulkan 1.2+ devices (de4668e4fa), which starts the descriptor buffer ring for the first time: pipelines past the push descriptor limit (32 on the RX 9070 XT) move from descriptor sets to descriptor buffers. The switch turns that off for the A/B against the HUD that goes missing with EDEN_NX2_NATIVE_STORAGE=0 (more descriptors per pipeline). The ring logs whether it is on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…RECT=0 for the front-wait A/B EDEN_NX2_QMD_VIEW=0: the front's QMD view never takes a table over, so every QMD signal holds semaphore acquires as before the view. EDEN_NX2_LIST_INDIRECT=0: the front reads every GPU-written compute list itself again (waiting for its writers and the GPU's copy). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Depth-only passes (shadow maps) had nothing in a capture; the pass's depth/stencil attachment is now dumped next to its colour targets (slot 8, named _zeta). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de, provoking vertex, line stipple and patch control points static For the A/B of the four rasterization states as dynamic state (fewer pipelines, a recorded command where the register changes) against holding them in the pipelines, on AMD's driver. The winner ships unconditionally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atform macros On Linux vulkan.h is built with VK_USE_PLATFORM_XLIB_KHR, so <vulkan/vulkan.h> pulls in Xlib, which defines Success as 0 and BadValue as 2. Since yuzu_cmd/yuzu.cpp includes vulkan_common/driver_call.h, GCC 13 fails on `case FileSys::Nup::PackResult::Success:` (yuzu.cpp:514, "expected unqualified-id before numeric constant"). present/layer.cpp already undefined both by hand. Undefine them in vulkan.h next to CreateEvent, None, True and the rest, so every file that includes it gets the same treatment, and drop layer.cpp's copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EventType::relaxed was fixed at creation, so an event that is a deadline only some of the time (a fallback timer) paid a precise wait on every run. It is now an atomic the owner sets with SetRelaxed, also from the event's own callback; ScheduleEvent and Advance's reschedule of a looping event read it each time, so the change applies from the next run, and the time already queued keeps its choice. Why it matters: the timer thread sleeps in the OS until os_wake_guard (one timer tick + 0.1 ms, 0.6 ms) before a precise event and waits the rest in a MWAITX monitor wait, which counts as CPU time. A high-resolution waitable timer on this PC (Windows 11, 0.5 ms tick) ends 261 us late at p50, 523 us at p99 and 663 us at p99.9 (4,000 waits of 0.1-3 ms, 2026-11-08), so there is no shorter guard: the remedy is fewer precise events. EDEN_NX2_TIMING_TRACE in the vrrtt-off field run logged 1,115 ms of precise waits per 2 s (core_timing.cpp:145), the bulk of HostTiming's 67% busy in cons7. os_wake_guard_override lets the new test pin the guard (Linux has none). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…events Each precise core timing event costs the timer thread a MWAITX wait of up to 0.6 ms (one 0.5 ms OS tick + 0.1 ms) before it, counted as CPU time. In the vrrtt-off field run (EDEN_NX2_TIMING_TRACE, 2 s windows) these events held precise waits that meet no deadline (counts and times per 2 s window): - HID::UpdatePadCallback: 3,075 waits, 549 ms (1,000 samples a second). A changed button, stick or trigger reaches the shared memory at once through npad_input_event (NPad::ControllerUpdate -> input_changed), so the periodic sample only steps the sampling ring. - the other HID samplers (touch, default, motion, mouse/keyboard) and Hidbus::UpdateCallback: ~21 ms, the same kind of poll. - nx2:vdec ready: 972 waits, 139 ms. It re-signals the decoder's auto-clearing ready event every 4 ms while the input queue has room; a beat that comes a tick late only makes the client check later by that much. They become relaxed events: the timer thread sleeps in the OS until their due time and runs them up to one tick late. That removes ~355 of the ~557 ms of precise waits per second. npad_input_event, KHardwareTimer (guest timers), sysmix output (audio) and the Switch 1 compositor stay precise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With host vsync, the presenter starts the game's frames (HostVsync::Displayed) and the nx2:vi2 vsync timer only checks whether the display stopped doing so (TimerVsyncSignals). It still ran as a precise event: in the vrrtt-off field run 807 precise waits, 137 ms per 2 s (EDEN_NX2_TIMING_TRACE), for a check that has no deadline. The callback now sets its event relaxed while the display drives frames and precise once it signals itself, through SetRelaxed. The first fallback run after the display stops can be one OS tick late (it already comes two vblanks after the last display start); from then on its runs are deadlines again. With EDEN_NX2_HOST_VSYNC=0 LastStart stays empty and the timer stays precise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Advance computed its OS wake guard from GetCurrentTimerResolution on every pass, a NtQueryTimerResolution system call ~5,000 times a second on the timer thread. The frontend already tells CoreTiming the tick it set (SetTimerResolutionNs, yuzu_cmd/yuzu.cpp:821-822 and qt_common.cpp:274-276, the maximum resolution, so no other process can make it finer); Advance now uses that and asks the OS only while none was set. timer_resolution_ns was stored and never read before; it is atomic now, as the frontend can set it while the thread runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stop
RunInput ended an input call after a fixed total of frames / 20 + ms / 1000 + 10 seconds, which
assumes the game presents at 20 fps or more. During a slow load (cons7 render gate, 16:9 session:
"Load: 80.0 s ... 105 frames (1.3 fps)" and the pause menu at 1.2 fps in eden_log.txt) a 40-frame
press (10 frames held, 30 released) needs ~35 s, so the call failed with "step 1 of 2 was still
running after 12 s (paused or loading?)" although the game was still presenting, and the harness
aborted the session. The docs already described the intended rule ("when frames stop for
timeout_s").
timeout_s is now the stall window (default 10 s, PresentStall): the call fails only when a frames
step waits and no present arrives for that long. ms steps run on wall time and never stall. The
MCP adapter waits for the longest call the emulator can make (ms steps plus timeout_s per awaited
frame). Unit tests: PresentStall over a 0.5 fps stretch and a stop; the adapter's answer wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports the guest JIT part of zortos293#717 (eb3553b, "Ocarina of Time performance work squashed on build 116"). Ocarina's guest cores run ~750 ms/s of JIT code each; these cut the code each block executes and its cache footprint. Each piece has an EDEN_NX2_* switch, on unless 0: - EDEN_NX2_JIT_FLAGS: branches, CSEL and CBZ/TBZ use the host flags the Add/Sub that set NZCV left, instead of reloading them from the guest state (cycle counting off only). - EDEN_NX2_JIT_DISPATCH: a 4-way set associative fast dispatch table of 2^16 entries (1 MiB per core instead of 16 MiB); 0 = old table, 8-24 = table bits. - EDEN_NX2_JIT_COMPACT_EXITS: unlinked exits jump to one shared stub per target, 5/6-byte patch sites instead of 22/23. - EDEN_NX2_JIT_FAR_CODE: fault/abort paths and exit stubs go to the last quarter of the code cache (its own commit front on Windows). - EDEN_NX2_JIT_FOLD_ADDR: [xN, #imm] folds into the fastmem address (unchecked 64-bit fastmem only); a fault adds the offset in its stub. - EDEN_NX2_JIT_ZEXT, EDEN_NX2_JIT_SPILL_GPR, EDEN_NX2_JIT_SCRATCH_COPY: no second zero-extension, spills to free callee-saved GPRs, copies instead of evictions. - EDEN_NX2_JIT_NO_FRAME: no per-block %rbp frame. - EDEN_NX2_JIT_BACKEDGE_HALT: links to a higher PC skip the halt check; backward links and indirect branches check it. - EDEN_NX2_JIT_RSB32: a 32-entry return stack buffer. - EDEN_NX2_HINT_PAUSE: YIELD and WFE pause twice. Port notes: zortos293#717's TSO loads (tso_ordered_loads, elide_ld_st_barriers, EDEN_NX2_TSO_LOADS) are the same transformation as round 7's x64_tso_barriers (LICHII_JIT_TSO): plain-MOV load-acquires and exclusive loads, no DMB LD/ST. They are not added again; the new code uses x64_tso_barriers, and the inline CNTPCT_EL0 (LICHII_JIT_INLINE_CNTPCT) is unchanged. Native LSE atomics (EDEN_NX2_JIT_ATOMICS: opcodes, frontend and the fastmem-fault CAS fallbacks) are left out: their fallback reports a CAS on a write-protected page as successful without writing it, the guest panic zortos293#831's author traced. Fix over zortos293#717: ZeroExtendWordToLong only aliases a value that is in a GPR, since a U32 spill slot holds only the 32 bits its spill stored and the 64-bit alias would reload all 64. The CheckBit-in-ZF path is off with very verbose debugging output, which emits code after every instruction. The switches are read where zortos293#717 reads them, after the accuracy settings, and only off Paranoid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqR9236AYzEMch9rerR4iU
e730f23 to
b5a9b43
Compare
Port the dynamic-rendering feature entry from PR zortos293#831 commit 42f55b8 so SetupAmdFfx uses one device feature chain.
Merge origin/nx2 without replacing the complete zortos293#805 split with its partial ports. Conflicting areas are resolved as follows: - gpu_split.{h,cpp}, dma_pusher.{h,cpp}, gpu_thread.cpp, engines/maxwell_3d.cpp, engines/kepler_compute.{h,cpp}, engines/maxwell_dma.h and their fast-method tests retain zortos293#805's write classification, QMD/front view, position waits, tick/idle hooks, fast method path and sampled method timing. Do not import zortos293#949's reduced acquire table, back_quiet drain scheme or duplicate zortos293#1028 write tracking. Keep the independent scheduler-owner check on CPU/service writes, the new RingFull drain site and opt-in live-read comparisons. - engines/puller.{h,cpp}, nx2.h and query_cache.h combine zortos293#805's authoritative front semaphore view and source-tracking unbounded waiters with zortos293#979/zortos293#1014's unbounded region table and direct-release shadow. Mirror releases only when ViewApplies is false: unsplit, back macros, or delayed/high-accuracy releases. The full front view must not consume a second mirrored counter absent from its original releases. Note both destination writes on the fallback path. Compute max/reduction releases retain shadow combination and zortos293#805 checks. Keep zortos293#874's synced payload handling and FencedPayloadLanding: synced callbacks never advance fenced_landed. - gpu.cpp, rasterizer_download_area.h, rasterizer_interface.h, memory_manager.cpp, buffer_cache.{h,base.h}, vk_buffer_cache.{h,cpp}, vk_rasterizer.{h,cpp} and buffer_cache_ports keep the full zortos293#805 read-back, upload ordering and tracing. Bring in zortos293#1028's review fixes: ClearDownload lands reads before DMA/discard writes, ClearReplacedDownload cancels only bytes reaching guest memory, and pending query results leave newer GPU writes dirty. Retain the new regression cases and the Qt-safe MayHoldGpuWrites boundary. Drop the unused reduced nx2_async_readback diagnostic implementation instead of duplicating zortos293#805's richer GPU-wait/read traces. - device_memory_manager.h/inc use zortos293#984's per-manager generation ranges with Map/Unmap increments and preserve DownloadLanding/write tracking. - nx2_profile.h and video_core.cpp combine zortos293#805's fine counters, sampling, scope depth and runtime switch setup with zortos293#965/zortos293#1039's zone-parent tracking, GPU waits, contention, worker/present/submit zones and stutter sites. - vk_pipeline_cache.cpp takes zortos293#1036's v2 cache, migration, jobs, parallel translation and emission. shader_environment.{h,cpp} keeps zortos293#970's unsafe TIC reads; also omit v2's new pool-wide pretranslation flush, since workers no longer flush descriptors. Keep the AMD e0a992208d87eeb8 exception in pipeline_optimization.h. - vk_graphics_pipeline.cpp, uniform_stream_reuse.h, sampled_views.h and tests converge zortos293#921/zortos293#949 with the adapted branch ports. PrepareImageView uses the single PreparesImage predicate, including valid views of NULL_IMAGE_ID. Keep uniform/descriptor delta replay, zortos293#974 fast-draw checks, zortos293#805 profiling and the empty-descriptor-range guard; no parallel duplicate paths. - vulkan_device.cpp and vulkan_memory_allocator.cpp combine cache-v2 device support and zortos293#880's backable-image failure policy with zortos293#836's synchronized recycler and zortos293#831's 50 ms memory-usage cache. Preserve all remaining branch AMD, query, shader, texture and fast-draw changes in the automatic merges. - nx2_video_decoder.cpp takes nx2's final dav1d/default-thread state. CMake test lists combine both sides; keep zortos293#805 fast-method tests rather than tests of rejected reduced split internals, and adapt waiter tests to >64 owners. Other matching cherry-picks converge to nx2; NX archives, HostTiming, memory/stutter/cache tooling and their sources are retained. Validation before commit: git diff --cached --check passed; 30 cache_metrics and cache_bench Python unit tests passed using workspace-local test data. The requested clang-cl ThinLTO/PGO build is in progress. BUILD_TESTING is OFF; C++ tests and in-game semaphore/read-back/cache/fast-draw validation remain separate. No emulator launched or stopped; no push or other branch changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Draft, opened early to track progress in the open. The branch is the consolidated build (nx2 + every finished item, 240 commits; native work in progress sits behind runtime switches that default off). The checklist below is the whole plan; ticked items are committed. Commits titled
EXPERIMENT (do not merge)hold A/B switches and are dropped before this leaves draft.Goal
Ocarina of Time (
0400486024DEC000) on an RX 9070 XT (RDNA 4, Adrenalin 26.10.2) and a Ryzen 7 9800X3D (Zen 5), on a 3440x1440 240 Hz OLED HDR display with VRR:Every change is benched on Windows with the game; the before/after table below is updated as items land.
Numbers so far
Updated 2026-11-08 23:00. Field (balcony above Kokiri village, camera pan) and walk (ladder, cutscene, village route) scenes at 16:9, one run per column. base = this branch's nx2 rebase before the native work (
allr); native (cons2) = the consolidated build at 994ba77 (GPU page table not yet active, bindless off); native (cons7) = the consolidated build at 6ce1332a03 with the GPU page table, the front-thread wait cuts (QMD view, GPU-kept compute list words, no drain behind narrowed semaphore reads), shader objects and the dynamic rasterization state, indirect-branch cache off. The base and cons2 columns were measured while the machine compiled (load 67–100%); cons7 ran with every compile paused, and the base build gets the same paused run in the final sweep.With the steady VRR presenter on, PresentMon sees the re-presented frames at the 224 Hz cap, so its lows and spikes describe the presentation, not the game frames; those cells are left out.
Where the frame goes now (cons7, field 62 fps = 16 ms/frame): the GPU is 80% busy (~13 ms/frame), and the guest CPU core 0, HostTiming and the GPU back thread each take ~10–11 ms/frame. Every one of them has to come down to 4.17 ms for 240 fps; the GPU per-pass profile and the shared translation cache are the next items.
Plan and progress
Native architecture (the main work now)
Everything that can run natively runs natively; items the new designs supersede are dropped instead of finished.
IsBufferDeviceAddressSupported()stayed false on Vulkan 1.2+, fixed); draw-input watch; exact barriers; per-draw state as dynamic-state deltas, with a shader-object path to A/B against pipelines. Open: the descriptor heap (bindless) fails device setup on 26.10.2 (VK_ERROR_INITIALIZATION_FAILED, being root-caused), then removing the binding memo. Dropped on measurement: GPU-generated draw runs (draw recording is 3% of the frame budget). Design:notes/native-gpu-backend.mdBench and tooling (outside the repo)
Loading, stutter
VK_KHR_pipeline_binary: driver binaries kept by the emulator.zsfiles (in the nx2: Ocarina of Time performance work (split GPU thread, caches, JIT, kernel), squashed on build 116 #717 split, in the consolidated build)CPU / real fps
zen5build presetlock or, FCMP flags without RCL, page-indexed block rangeszen5codegen (-march=znver5) is the default for every Windows bench build; A/B against generic in the sweepGPU / RDNA 4
EDEN_NX2_GPU_MARKERS: named passes/draws for RGP captures.FMZwith half operands translateGENERAL+TRANSFER_DST(A/B switch in the build)Present, pacing, latency
present_wait2pacing; "Variable Refresh Rate Display" settingVK_AMD_anti_lag) at that pointImage quality
Ultrawide 21:9
Movies
Correctness and stability
<package>_CUSTOM_DIR; sirit builds with libc++ 23Merge readiness
-DENABLE_WERROR=ONbuild +ctest(first full run: two build fixes in, 4 of 1149 tests failing and being fixed),tools/clang-format.shcleanEXPERIMENTcommits dropped, winners kept unconditionallyAMD driver bug found on the way
Credits
Ultrawide techniques from Ivan0xFF's oot-mod (MIT), Hookshot and NX2navi. Ported with their authors kept: #180, #243, #313, #321, #337, #354, #408, #559, #573, #579, #655, #678, #695, #717, #769 (opt-in part), #784, #785.
🤖 Generated with Claude Code