Shadow storage noted a GPU write with the whole range its binding names: the copy-in skipped that range and
the fence copied all of it back, so CPU writes to other fields of the same records never reached the GPU.
Ocarina's lighting parameter is such a field: the lighting came out NaN and the fog pass drew the scene
black. Each page a noted range covers now keeps a base copy, the copy-in moves only the bytes the CPU
changed and the write-back only those the GPU changed (vk_memory.c merge_page). The mirror's merge shader
marks the 4 KiB pages it changed, and one job per submission copies only those back (shadow_wb_cb); the
copy-in reads the CPU-side generation tables through the shared write-watch snapshots; and a caller that
reads guest memory itself resolves with vk_mem_lookup, which does not queue the pool for the copy-in.
Pull request #394 by jacc. Its changes are taken as they are; one hunk met the lazy write-back that came
later (vk_gpu_written_back) and is merged by hand. Added here: the lazy write-back stays off with shadow
storage, because its fetch copies the mirror into the pool's buffer, which is guest memory only for an
imported pool. Files other sessions are changing: runtime/vk_memory.c (the shadow section, the merge
record, vk_gpu_written_back, wb_lazy) and three one-word call changes in runtime/vk_usnap.c.
Without shadow storage (Windows outside Wine) the merge pipeline gains an unused fifth binding and two
push constants and nothing else changes. The Windows files compile; nothing was run, on Windows, Wine or
Linux.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Linux overcommits, so plat_commit_enforced was 0 there and nothing the runtime does when memory runs short
ever ran: no stale-texture drops, no idle or disuse eviction. plat_commit_info on Linux now reports the
limit with the least headroom of the cgroup v2 limits the process lives under (memory.max, memory.high,
against memory.current less its file cache), the machine's RAM (MemAvailable against MemTotal) and, with
vm.overcommit_memory = 2, the commit limit; a read is reused for 250 ms and the first one is logged. The
spare VRAM blocks stand down under low memory only where device memory counts against that limit
(plat_commit_counts_vram: Windows, and any integrated GPU). Windows behaves as before.
Pull request #378 by mooki000, taken as it is. The Windows files compile; the Linux code is not compiled
or run here.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The first part of this pull request (the cache beside the exe with an 8 GiB limit) was already in. This
is the rest: nothing is set without NVIDIA's kernel driver, neither variable is set when the folder cannot
be made or written (the driver caches nothing in such a folder), the log says which folder and limit hold,
and a game run sets the drivers' cache variables in main before any of its threads can load a Vulkan
driver (vk_device.c vk_driver_cache_env); the other modes still set them in create_instance. On Windows
that only moves the MESA_SHADER_CACHE_DIR setenv earlier.
Pull request #369 by mooki000, taken as it is. The Linux part is not compiled or run here.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
tools/make_game.py has its own compile step and did not build runtime/*_spv.h, which the repository
no longer holds: from a source checkout (the Linux path of docs/README-BuildKit.md) every runtime file
that includes one failed to compile. A kit carries the headers built, so nothing is stale there and no
shader compiler is looked for.
Pull request #405 by mooki000, taken as it is. Not run on Linux here.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
NVIDIA's Linux driver compiles into a disk cache in ~/.cache/nvidia, shared with every program, and a process adds at most
1 GiB to it (__GL_SHADER_DISK_CACHE_SIZE); a start that finds more than that in several files deletes the later ones.
nx2navi.exe --prepare-shaders builds the 42,142 variants of game 1.1.1 in one process, about 1.5 GB: on the default limit it
stored about 30,700 of them, and the first start after the preparation compiled the rest (and the pipelines built from
them) again.
Unless the environment sets them, the game now points __GL_SHADER_DISK_CACHE_PATH at nvidia-cache beside the exe (made
here, the driver only makes GLCache inside it) and __GL_SHADER_DISK_CACHE_SIZE at 8 GiB before its first Vulkan call, as it
does for Mesa's cache. The docs name both.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NX2NAVI_ANISO was read once, when the first sampler was made (vk_texture.c sampler_features), and make_sampler raised the level of the surface textures' samplers as it created them. Samplers are looked up by the game's parameters and live until shutdown, so a new level could only reach them after a restart, and the setting had no row in the menu.
The decision which samplers get the level moves from make_sampler into aniso_raised, and sampler_for puts the raised level into the sampler key, as it does with the DLSS texture bias. vk_sampler_aniso_live reads the setting again after a settings change, at each present beside vk_dlaa_frame's bias; on a new level it clears the slots' samplers and bumps the sampler generation, as vk_sampler_dlss_bias does, so the slots and the descriptor caches take samplers of the new level from the next draw. The old level's samplers stay until shutdown like every sampler. The setting is marked live in cfg.c, and the Screen tab gets a Texture filtering row (Game, 2x, 4x, 8x, 16x) with the description the catalog already had.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the pacer divides the limit by frame generation's factor, the default limit of 60 held the game to 30, 20 or 15 rendered frames a second at 2x, 3x or 4x on any monitor, 15 at DLSS 4x on a 165 Hz one. While AMD's or NVIDIA's frame generation swapchain is in use and neither fps_limit nor the older fps60 = 0 is set, the limit is now the monitor's rate, as with fps_limit = display: 4x on 165 Hz draws about 41 and shows 165. A limit the player set is kept, and the menu's Frame rate limit row shows the display choice while this applies.
The mouse camera divided its input by the limit's frame ratio, which counts generated frames, so frame generation turned it faster by its factor; it now uses the rate the game draws at (frame_step_game_hz).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make_launcher.py --dlssg needed --streamline, a Streamline SDK folder unpacked by hand, and make_kit.py could not
carry DLSS Frame Generation at all, so a game built from the kit always left it off. tools/streamline_pin.py now pins
the Streamline 2.14.16 SDK release (its archive's size and SHA-256, and the SHA-256 of each file the game loads):
without --streamline the scripts download the archive once, check it, keep only sl.interposer.dll, sl.common.dll,
sl.dlss_g.dll, sl.pcl.dll, sl.reflex.dll, nvngx_dlssg.dll and the two licence texts, each checked, and delete the
archive. A folder given with --streamline must hold the pinned files.
The bin/dlssg/ assembly moves from make_launcher.py into streamline_pin.place, so the release and the kit write the
same set and the same dlssg-NOTICE.txt: a kit made with --dlssg and the 0.6.0 release's nx2navi_dlssg.dll has a
bin/dlssg/ identical to the 0.6.0 release's, byte for byte. make_kit.py takes --dlssg [--streamline] like the
launcher. nx2navi_dlssg.dll itself still comes from tools/build_dlssg.py.
tests/launcher_test.py test_streamline_pin covers the extraction, the cache, refused archives and folders, and the
notice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
framegen_multiplier (2, 3 or 4, live) sets how many frames the display gets per rendered frame while NVIDIA DLSS frame generation runs, and a DLSS multiplier row under Frame generation in the Screen tab sets it. nx2navi_dlssg.dll now passes that count to Streamline and reports the GPU's limit (numFramesToGenerateMax) through a new optional export, nx2sl_frames_max; with an older DLL the game keeps to 2x. A value above the limit is clamped to it with a log line, and the menu lists only what the GPU and driver allow, with a note saying why the rest are missing. The Frame generation row now says "in use: off" and why when DLSS frame generation was set but does not run.
The pacer divides the frame limit by the multiplier in effect, as it already halved it for FSR 3.1, so the display gets the limit and VSync at the display's rate drops no generated frames; RivaTuner Statistics Server stays out while DLSS frame generation runs, since it counts generated frames. With DLSS 2x a limit of 60 now shows 60 on screen instead of 120.
nx2navi_dlssg.dll has to be rebuilt (tools/build_dlssg.py) for 3x and 4x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commit census of the perf trace walked the address space on the presenting thread outside mem_census, which
brought back the hitch mem_census was added to remove; it now runs only with mem_census = 1. DLSS's output pass at
the mode's share fell back to no pass at all when the scene was not at that share (the first presents, a dynamic
resolution step): after such a frame the first pass wider than the scene is taken again for 120 frames. The launcher's
nx2navi.ini editor no longer rewrites a file it cannot read, keeps the byte order mark and bytes that are not UTF-8,
reads an empty line as the game does, and leaves no temporary file behind. The build kit carries the two modules
build_game.py imports on Linux, the Linux steps list the right packages and Python, and stale comments and notes are
corrected (Discord on by default, two prebuild threads, Enhanced HDR gone).
DLSS Frame Generation needs nx2navi_dlssg.dll, NVIDIA Streamline's runtime and nvngx_dlssg.dll beside the game, which
no release carried, so the menu offered it and the game turned it off. tools/make_launcher.py --dlssg and --streamline
put them with their licences and a notice in the release's bin/dlssg/, and tools/make_game.py place_dlssg copies the
whole set beside the game. THIRD_PARTY_NOTICES.md has the row; the notes and README say RTX 40 and 50.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux build linked libunwind for a frame walk the runtime no longer uses: platform/linux.c walks the stack with
libgcc's unwinder. make_game.py's Linux toolchain imports build_game.vma_include, so the kit carries that file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make_game.py only knew MSYS2. On Linux it now does what tools/build_game.py's Linux path does:
- clang, clang++ and lld from PATH (checked at start), the system's headers and libraries, vk_mem_alloc.h from the
system or the pinned copy build_game.py fetches into toolchain/vma;
- the runtime's *_sdl.* files and platform/linux.c in place of the Win32 ones; no shim.c (ui/menu_sdl.cpp defines
menu_sdl_event), no windres, no DLLs to copy;
- -fdeclspec for C++, as third_party/ffx_api/ffx_api.h declares __declspec(dllexport) unconditionally;
- build_game.py's Linux link flags (-no-pie, noexecstack, rpath $ORIGIN, libstdc++ static) and libraries, plus
dav1d (the AV1 movies), libatomic and libunwind (the previous commit's plat_frame_capture);
- ocarina and nx2navi_xlate without .exe; the translator from the kit's bin/ or a checkout's build/xlate.
Windows is unchanged: every difference is under os.name.
tools/make_appimage.py packs the kit (tools, recomp, runtime, the built translator, VMA) with a relocatable CPython 3.14
and numpy, lz4, zstandard and fonttools into NX2navi-Linux-<version>-x86_64.AppImage. Its AppRun checks for the
compiler and headers and names the packages for Arch, Fedora and Debian/Ubuntu, installs zbic (GPL-2.0, not bundled)
with pip into ~/.local/share/nx2navi/pydeps on the first run, picks --jobs from the memory, builds and writes play.sh
beside the game. README-BuildKit.md has a Linux section.
Tested on CachyOS (kernel 7.3, clang 23, RTX 3080, NVIDIA 620.70.06, 16 GB): make_game.py --nup ... --jobs 4 builds
ocarina in 9.9 minutes from a 1.1.1 dump, which passes --probe-cfg; a build with the same sources and flags
(compiled by hand before this script could) starts, loads a save and plays. With one compile per thread on 16 GB
the build filled memory and swap and the kernel stopped desktop processes, hence the --jobs advice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make_launcher.py and make_kit.py take --prompt-art (folders holding XGamepad, P5Gamepad and Keyboard_Mouse, or those
folders) and copy only the pictures pad_prompts.py reads into the release's prompt-art folder; tools/prompt_art.py
holds the art tables and that list, with no imaging packages. make_game.py's new last step, prompts, runs
pad_prompts.py on that art into the game folder's romfs-patches-* folders. Its key names everything it reads (the
RomFS, the art, the tools, the bind_ lines of nx2navi.ini, the folders being there), so it runs again only when one of
those changed, and it makes no other step run. A kit without art skips it. The launcher's Python gets fonttools and
pillow, and its jobs show a Draw the Button Prompts row when the version has art.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release carried AMD's FSR 3.1 library but not nvngx_dlss.dll, so DLSS ran only where the NVIDIA driver supplied the model itself; elsewhere players had to fetch the DLL by hand. tools/make_launcher.py and tools/make_kit.py now download NVIDIA's own build from the DLSS SDK repository at a pinned tag (tools/dlss_pin.py: 310.9.1, checked against its size and SHA-256, cached), and put it in the release's bin/ with a notice naming the NVIDIA RTX SDKs License that covers it. --dlss FILE takes a copy on disk instead, --no-dlss leaves it out.
tools/make_game.py copies the DLL and its notice beside the game. A different nvngx_dlss.dll the player put there is kept; the one an earlier build placed (named by the notice's sha256 line) is replaced, so a newer release can bring a newer DLSS. The compile step's key covers make_game.py and the kit version, so existing installs get the DLL with their next update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game's ACES tonemap shader gets a GLSL function linked into its SPIR-V where it clamps each channel: the game's SDR result upgraded to the HDR range by luminance and mapped to the display with the RenoDRT curve, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare and hue correction. The settings reach it as four vec4 the support buffer gains at 1440. The present encode is a port of the swap chain pass: gamma correction, gamut compression, the peak clamp and PQ. Without an HDR10 swapchain the shader gives the game's own SDR picture; native HDR is unchanged.
The menu offers Off, RenoDX and Native, with gamma correction, the tone mapper and its grading rows. The shader build compiles sources marked "keep functions" without optimisation so their functions survive for the splice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In-game menu: the settings window, its tabs, rows, buttons and cursor, the performance overlay and the Preparing
shaders box are drawn with the game's interface textures where the RomFS holds them (runtime/ui/menu_skin.cpp,
perf_panel.cpp; plain shapes until they are decoded). Restart joins Continue and Quit, each behind a confirmation;
VSync is an on/off switch; DLSS is listed only on NVIDIA GPUs; the menu sounds are quieter; Picture shape is gone and
the setting descriptions are shorter; edge cases of input, layout and fonts are fixed. tools/menu_preview.py draws the
menu without the game.
Launcher: Import Saves takes a save from the game folder's saves/ folder or one the player picks; downloads fall back
to Windows' own certificate check when Python's refuses a server; updates are faster; the title bar is drawn in the
window and Navi is every icon. tools/make_game.py builds with CLANG64's clang where the launcher installed it, packs
the RomFS beside the lift and the compile, takes a prebuilt runtime, and compiles the game's resources without cmd.exe,
so a user folder with a space in its name builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NX2navi-Setup.exe installs the launcher into %LOCALAPPDATA%\Programs\NX2navi without administrator rights, with
shortcuts and an Apps entry. The launcher finds the player's .nup, downloads the build tools itself (the MSYS2 UCRT64
packages toolchain.lock.json pins, checked by sha256 and unpacked without pacman, and zbic from PyPI) and runs
tools/make_game.py, whose steps it shows with their progress. Its window follows the game's options menu: colours
sampled from it, the menu's filigree, the white glowing cursor, the Triforce in the title band and as the programs'
icon, and all text in the game's Cetra Display, decoded from the player's own dump. Mouse, keyboard and an XInput
pad drive it.
Updates come from GitHub releases (NX2navi-update.json): the new version is downloaded, checked and staged beside
the running one, the stub restarts into it and falls back to the previous version when it fails to start, and the
game is rebuilt without the dump. make_game.py now keys its exefs and romfs steps on the game files alone, has
--rebuild and --events, compiles only the files whose source, headers, flags or compiler changed, and replaces
ocarina.exe only once the new one links. tools/make_launcher.py builds the release on Windows or Linux.
Tested: tests/launcher_test.py (19 tests), the real toolchain download and install, and under Wine 11 the setup,
the stub, the window on the bundled Windows Python, an update from a local feed by clicking Updates, the rollback
of a version that fails to start, and the downloaded clang compiling and linking a program against the game's
libraries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged onto the current port: make_game.py keeps the launcher's step keys, cached link and in-place swap, and its
own runtime manifest (windres), AMD's FidelityFX library and the shader preparation, which becomes a step of its own
("shaders") that the launcher shows in its install, rebuild and update jobs. make_launcher.py ships the shader tools
and, with --ffx, AMD's library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README takes the name NX2navi as on GitHub and lists the new upscalers, frame generation, the HDR modes, the
higher resolutions and the shader preparation, and points to the build kit for installing. A crash calling address 0
now logs the return address on top of the stack and its module (it was inside sl.common.dll for Streamline).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shader reader now reads Switch 2 (NVN2) programs, whose 0xe0-byte control sections and task and mesh stages the
runtime keys as it does, and runs on Python 3.12 through the zstandard package. The kit's new last step writes the
36,031 shaders of the player's game files as a seed and runs ocarina.exe --prepare-shaders, which translates them and
has the GPU driver build each one's library, about 5 minutes. On a cold walk into play this took the stalls from
14 seconds of pipeline builds and 9 of translations to none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/make_game.py checks the title and main's build id (1.1.1), stores the ExeFS modules plainly (ZBIC through the
player's own zbic package), packs every RomFS file straight from the container into romfs.bin and index.json after
checking its sha256, lifts with recomp/patches-v111.txt plus the two SDK bring-up fixes, and compiles and links with
MSYS2's clang into <out>/ocarina.exe with the DLLs it loads and the shader translator beside it. Finished steps are
skipped on a rerun. tools/nup.py reads the container; tools/make_kit.py assembles the kit (sources, tools, the
translator, README-BuildKit.md) and refuses it when check_content finds game content.
Measured from an empty folder: 5.4 minutes in all on a 16-thread Ryzen 7 (RomFS 25 s, lift 48 s, compile 237 s), and
the result plays into Kokiri Forest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>