On 1.4.3 the per-frame wait is the second of the two nvnSyncWait calls in f_70002db8ac (return address 0x70002db99c). Like on 1.2.1, its sync comes from a two-entry ring indexed per frame; the first call waits on one object's sync behind a mode check. wdiasjunior found the site and measured it on an RX 7800 XT (#676): about 67-70 to 81-83 fps on Great Sky Island.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each frame the game waits in one nvnSyncWait (1.2.1 lr 0x7000b23dfc) for the frame it presented last. On the Switch
that work is long done by then; here it is still in the replay and on the GPU, and on Great Sky Island the wait is
about 4.6 ms of a 13 ms frame. With TOTK_SYNC_AHEAD=1 that one wait waits for the frame before it instead, once the
replay thread submitted the asked value, so the game builds the next frame while the GPU runs the last one.
The game then writes guest memory that GPU work still in flight reads. Shadow storage makes that safe: a shadow update
of a pool with a reader in flight is queued and copied by the GPU in the next part's prologue, in queue order, instead
of memcpy'd under that reader (TOTK_SHADOW_DEFER=1, the default with the switch; 0 makes the replay thread wait for
the reader instead). The guard is read under the shadow lock and the wait takes that lock once after raising it, so no
sync that read the old guard is still copying when the game writes again. The switch needs shadow storage (Linux and
Wine by default, TOTK_SHADOW=1 on native Windows) and the 1.2.1 call site; otherwise it stays off with a log line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared runtime code now goes through runtime/platform/platform.h, implemented by platform/win32.c and a new
platform/linux.c: futex locks, a userfaultfd and PAGEMAP_SCAN write watch, signal-based async preemption, our own
fiber switch, and a SIGSEGV handler for the thaw and crash hooks. The window, input, menu and software keyboard get
SDL3 back ends beside the Win32 ones, with the swapchain (gfx_present.c) and input merging (input.c) shared. SHA-256
replaces BCrypt, ELF builds get their own override sections, and the build tools gain Linux branches.
On Linux the selftest and the gfx-abi, astc, nvn and window probes pass. probe-vkmem still fails: imported guest
memory cannot be write-watched on Linux, which the shadow buffers in vk_memory.c will address.
Comments cited codes from local notes (port-lessons P1, backlog), task labels (T6c1-T6c3, M4), bare upstream commit
hashes and issue numbers, a file that no longer exists (nvn_api.c, now nvn_api_*.c), a generated file (main_0103.c)
and line numbers into upstream files that refs/ has moved past, so they pointed at unrelated code. Those now name the
file, function or project and say what the cited code does. Attribution of copied code keeps its pinned commits.
Corrected facts: TOTK_REPLAY_ASYNC defaults to 0, and the shader cache, pipeline cache and pipeline keys live next to
totk.exe, not in build/v121.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.4.3's GPU heap allocator creates a physical pool with flags 0x18c (CPU_CACHED, GPU_NO_ACCESS, COMPRESSIBLE): it
keeps the game's storage and gets device memory like the 0x189 pools of 1.2.1, which virtual pools alias. The CPU
never writes that storage (its hash was unchanged over 3600 frames of the intro), so nothing is synced.
nn::aoc::ListAddOnContent, which 1.4.3's main calls at start, returns no add-on content instead of running the SDK
on an uninitialized aoc client.
probe-ipc runs the 1.4.3 SDK's renderer semaphore constructor (sdk+0x221fb0) instead of the 1.2.1 sf heaps, and
expects SDK 20.5 to keep its audren:u manager session open. probe-services checks ListAddOnContent.
package_build.py takes --version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imported pools put vertices, indices, constants and storage buffers in system memory, so discrete GPUs read them over
PCIe on every draw. A pool now gets a device-local mirror on its first use as a vertex, index, uniform, storage or
indirect buffer; bindings, buffer copies, fills and uniform updates use it at the same offsets. The first access of a
submission syncs the pages written since (write-watch snapshots shared with the texture checks, plus
nvnMemoryPoolFlushMappedRange ranges) in a prologue command buffer before the part; what the GPU writes into a mirror
goes back to guest memory at the end of the submission. TOTK_MEMORY=import|mirror|verify overrides the device class;
verify compares sampled mirror pages with guest memory at the fence. The vkexec probe gains a mirror part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At the start of every submit the dirty census queries write watch over the imported pools the replay bound as vertex,
index, uniform, storage or indirect buffers, stamps the shared page generations and logs eager, lazy and first-use
bytes per frame and per submit by pool class as mem: dirty every 60 frames, with FlushMappedRange calls and bytes and
the live and cumulative mirror size. The device class line (uma or discrete, heaps and memory types) is logged once at
device selection and the pool census also at frame 600. perf_summary.py summarises the new lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nvn_pool_invalidate was a weak no-op, so nvnMemoryPoolInvalidateMappedRange never wrote anything back. TotK
invalidates its 260x260 R16 grass maps and 1x1 targets before reading them; they now go through guest_read.
The grass system also renders a 128x128 RGBA8 cut map into a CPU-cached pool every frame and reads it with no
invalidate. Without its texels the CPU saw no cut: swings left the grass standing, with no blades and no insects.
After nvnSyncWait or nvnQueueFinish, dirty mirrored targets up to 64 KiB rendered in a submission up to the waited
fence go back to storage (cpu_synced, vk_texture_write_back_small), unless the CPU rewrote it since.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>