Commit Graph

41 Commits

Author SHA1 Message Date
Vasilis Manetas
eaf10677fa Import guest memory as dma-bufs through udmabuf on Linux instead of keeping shadow copies of it.
On Linux every CPU-visible pool had a host-visible shadow copy, because amdgpu treats a host-pointer import as a userptr object that it looks up again at every submission and invalidates whenever the write watch or a freeze re-protects its pages. Now the watched reservations are shared mappings of one memfd, /dev/udmabuf makes dma-bufs of page ranges of it, and the device imports them (VK_KHR_external_memory_fd, VK_EXT_external_memory_dma_buf). A dma-buf pins its pages once and is not tied to the process's page tables, so protection changes leave the GPU's mapping alone, and nothing is copied.

RADV puts every imported memory object into every submission, so pools share their imports: guest addresses are cut into 64 MiB spans (NX2NAVI_DMABUF_MB), and each pool is a buffer bound into its span's memory at its offset. A new span is cut back at the spans already there, so spans overlap only where a pool itself lies across an older span's edge, since amdgpu counts every span's pages in its GTT use. A decommit punches the pages out of the memfd, or zeroes them in place where a dma-buf pins them, and the pages a freed span pinned that nothing committed go back to the system.

It is on by default where /dev/udmabuf opens, udmabuf's size_limit_mb is 1024 or more, the device has both extensions and NVIDIA's kernel module is not loaded. A start-up probe imports one page as the pools will be; any refusal keeps the shadow copies, and the log says why. NX2NAVI_DMABUF=0 keeps the copies. tests/platform_dmabuf_test.c checks the shared mapping without a GPU, and docs/building.md ("Memory") describes the imports and what they need.

RX 6800 XT (RADV, Mesa 26.2.6), one build, interleaved pairs with NX2NAVI_DMABUF=1 and 0: Kokiri Forest 60.7 against 40.3 fps (3 pairs, +51%, standard error 0.4), 1% low 50.9 against 34.0, frames over 25 ms 5 against 768; Hyrule Field 55.7 against 42.4 fps (2 pairs, +31%, standard error 0.4). No pool was left without a span, no span was refused, and --probe-vkmem passes with and without the imports.
2026-11-12 15:00:27 -05:00
chrg6
3529eff949 Offer both kinds of HDR in the menu again: the tone mapped picture and the game's own.
The HDR row lists Off, RenoDX (the game's picture tone mapped to the display, the one fixed look) and Native (the game's own HDR, taken up at the next start), with the peak, game and HUD whites under either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:06:30 -05:00
chrg6
309e412f1f Make HDR one fixed look with true blacks, and keep only its on switch and the three whites as settings.
HDR on is the game's scene through the observer-response mapper, anchored to the game's own mid grey and tone slope, with half of the hue bend of the game's clamp: the defaults the menu had. The tone mapper choice, the mid grey, the tone slope and its match, the hue clip, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare, hue, grade and gamma are gone from the menu, the settings table and the example ini; a file that still sets them works as before, the keys read by nothing and logging nothing. The menu's HDR row is Off and On, with the peak, game and HUD whites under it. The game's own HDR is still there for a file that sets hdr = native, and the row lists it only then. The luminance mapper and its grading code, which no setting reaches any more, are removed from the tonemap library, and the support buffer carries one vec4 for it.

The dark tones of a dark scene sat far above black: the present showed the game's sRGB-encoded picture as that encode decodes, linear near black, while the game is graded for a display of gamma about 2.2, and since the observer-response mapper became the default the 2.2 correction ran with the luminance mapper only. A scene value of 0.003 came out at 0.29 nits at a white of 350 nits, where a 2.2 display shows the game at 0.015. The present now always shows the picture as a 2.2 display would, and the tonemap aims its peak lower by that correction's inverse, so the brightest light still reaches the display's peak; the same value comes out at 0.017 nits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:06:30 -05:00
chrg6
3d394dd84e Merge the fixes line: the stutter, memory, flicker and crash fixes, co-op, the mesh shader fallback and the launcher fixes.
Brought onto the line with FSR 4, its frame generation and the debug menu. Where both sides had added to the same
place, both stay: the patch table keeps the pickup count and the free camera, the settings list keeps both sides'
names, the menu includes the co-op page and the debug menu, and the launcher builder takes the pipeline list and the
FSR 4 folder. Both sides had added the menu's typed-character event; one definition stays, with the window code that
passes characters outside the basic plane.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 08:18:01 -05:00
epicmeme546
5bdf583331 Add frame generation through AMD's FSR SDK 2.4 as a setting to try with FSR 4, and never generate a frame without the HUD-less picture.
runtime/vk_fg4.cpp makes the frame generation swapchain and context with the libraries FSR 4 comes with
(amd_fidelityfx_loader_vk.dll and amd_fidelityfx_framegeneration_vk.dll beside the executable). vk_fg.cpp takes
that path only with fsr4_fg = 1 while FSR 4 is the upscaler; the default stays the FSR 3.1 library. The loader
picks the version for the card: the machine-learned FSR Frame Generation 4 on Radeon RX 9000 cards with Windows 11,
which is untested, and 3.1.6 elsewhere. On an RX 7900 XT (driver 26.10.1) it lists frame generation 3.1.6 and the
swapchains 4.0.0, which the driver brings and the loader chooses, and 3.1.7. The swapchain in place is wanted only
while its library is the one to use, so a change of the setting or of the upscaler, or a failure of this path,
takes it down as switching frame generation off does and the next present makes it with the other library.

The preparation gives this library the camera's position and axes, which FSR Frame Generation 4 requires, and a
scene scale of one metre a unit: with the 0.01 the FSR 3.1 path uses, version 3.1.6 drew grey translucent shapes
into the sky of its frames while the camera turned (160 captures of the screen during a camera sweep; clean with 1).

Both paths now generate a frame only when the picture without the HUD is there for it: generated from the
presented picture alone, the HUD would be warped with the scene behind it. In four configurations (21:9 with either
library, a 16:9 picture in a 21:9 window, a 1080 picture without HDR) every generated frame had it, and the log
counts the frames without. On screen, of the 6,404 pixels of the HUD's item button that stayed fixed through a
camera sweep with the FSR 3.1 library, 6,162 stayed fixed with this one.

Standing in Hyrule Field at 3440x1440 with FSR 4 Quality the game draws 43.0 frames a second with this path and
42.2 with the FSR 3.1 library, each shown twice over. One run changed the present mode, HDR, fullscreen, frame
generation, FSR 4, this setting and the upscaler in turn: 216 screenshots without a bad frame, and no failure.

The SDK's frame generation headers join runtime/third_party/ffx_sdk, unmodified. tools/make_kit.py and
tools/make_launcher.py take the third library from the --ffx4 folder when it is there, and tools/make_game.py
copies it beside the game.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 04:49:01 -05:00
epicmeme546
4d5454d380 Add AMD FSR 4 upscaling through the libraries of AMD's FSR SDK 2.4, as an Upscaling choice beside FSR 3.1.
runtime/vk_ffx4.cpp loads amd_fidelityfx_loader_vk.dll from beside the executable and asks it which upscaler
versions the device can run. Where a 4.x version is listed (Radeon RX 6000, RX 7000 and RX 9000 cards with AMD's
driver 26.10.1 or newer) the menu's Upscaling row gains AMD FSR 4 Native AA, Quality, Balanced, Performance and
Ultra Performance, and fsr4 = 1 runs it in FSR 3.1's place with the same inputs. Its contexts are made with that
version asked for by its id and the provider is asked afterwards which version it runs, so no other upscaler can
be shown under the name. When a context or a dispatch fails, FSR 3.1 makes the frame, and after three failures
FSR 4 is off for the run. Each upscaler starts its history anew on the frame after a change from one to the other.
The setting is off by default and the Linux build compiles stubs.

On an RX 7900 XT (driver 26.10.1, 3440x1440, HDR) the library runs FSR 4.1.2: a context takes 54 ms to make and
217 MiB of video memory. Standing in Hyrule Field: Quality 47.8 fps where FSR 3.1 gives 51.5, Native AA 41.4.
Screenshots of 601 consecutive frames standing, 801 during a camera sweep, 701 each in Kakariko and the
graveyard, and 1,301 across changes between FSR 3.1 and FSR 4, their modes and off show no flicker that FSR 3.1
does not have, and it runs together with FSR 3.1 frame generation.

The SDK's API headers are in runtime/third_party/ffx_sdk, unmodified, with the licence. tools/make_kit.py and
tools/make_launcher.py take AMD's two signed libraries with --ffx4, and tools/make_game.py copies them beside the
game when the kit has both.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 03:22:08 -05:00
chrg6
420aef041a Correct what a review of this intake's commits found.
A second reading of the commits taken in from pull requests found these, each traced in the code:

- heap_lazy: the camera speed's search read the whole heap, which with the setting on committed every page and undid what it saves; a scan now steps over the pages the lazy heap never committed (mem_untouched_run). tests/frame_step_check.py and tests/save_share_check.py no longer linked (frame_rate.c and fs_host.c call the new memory functions; the second also missed mem_probing before); both pass again. The settings text says that with the setting on a PC out of commit ends the game in play, not at start, and the census comment is back over its own function.
- Pipeline libraries on AMD's older Windows driver: the condition is the driver for RDNA 2 and older (RX 400 to RX 6000), not RX 5000 and RX 6000 only, and the texts named a price the code does not have. With libraries off a new pipeline is built by a worker while its draw is left out, and the start waits for every recorded pipeline instead of 20 s at most. The texts say so, and that the finding is one player's on one card. The menu strings nothing shows are as they were.
- Page file hint: it comes only when Windows sizes every page file, and it names the initial size and the automatic management that must be unticked first.
- Frame generation note: it keeps "needs an RTX 40 or newer" for the players it is right for and points the others to the log; the bridge's line says it is Streamline's general support check.
- Comments and settings texts that still described the old behaviour: the process commit bound, the wait with shadow storage, the scratch's least size, the action prompt under auto.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
(cherry picked from commit 19f03aa01b26d6e17fb5fc70ad987aa8c68e8225)
2026-11-11 00:39:50 -05:00
chrg6
7145c3f208 Say in the log when the system's commit runs low under a page file that Windows sizes itself.
With the page file left to Windows the commit limit stays near the installed memory plus what the file held at the start, and on a 16 GB PC with a 16 GB card it ran low in Hyrule Field at 32 GB, where a fixed page file of 32 GB or more gave 49 GB and no low commit. The first "system commit low" line of a run is now followed, once, by a line that names the cause and the Windows setting, and the build kit's guide gives the same advice for 16 GB PCs. plat_page_file_managed reads the PagingFiles value once; on this PC (page file sized by Windows) it returns 1.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 23:14:00 -05:00
chrg6
4af930d0c2 Ask for the disk space the build really needs, and say how long an install really takes.
The launcher asked for 31 GB where the build's own check now asks for about 34, so a disk between the two passed the first check and was refused seconds later. Both say 34 GB now. The build no longer asks for the whole shader preparation's space when the game folder already holds translations. The texts said 10 to 25 minutes for an install that took players 27 to 41, and 5 GB for a build cache that measures 1.6. A build error's message goes into the launcher's log as one line.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:12:52 -05:00
chrg6
0e5e40b620 Merge branch 'claude/linux-old-headers' into test-all-fixes 2026-11-10 14:18:29 -05:00
chrg6
61cd4784f8 Build the Linux kit against pinned Vulkan headers and without #embed, and try the compiler before the long steps.
A player's AppImage build of 0.7.1 stopped in its compile step after eight minutes of RomFS work: four
files did not compile, because the kit builds with the system's compiler and headers and theirs were
older than the runtime needs. vk_pipeline.c and vk_device.c name VK_KHR_pipeline_binary,
VK_KHR_dynamic_rendering_local_read and VK_KHR_shader_maximal_reconvergence, which their Vulkan headers
did not declare, and ui/menu.cpp takes its font in with #embed, which clang has from version 19.

- Vulkan headers: tools/vulkan_pin.py holds the release the other builds use (1.4.363, the archive and
  hash tools/make_release_linux.py already pinned, which now reads them from there). The AppImage carries
  its C headers in toolchain/vulkan-headers and builds against them whatever the system has; a kit or a
  source checkout uses the system's when they are at least that release and fetches the pinned ones
  otherwise (build_game.py vulkan_include). Their folder goes first on the include path of the runtime's
  sources only, so the lifted code's objects keep their keys, and a runtime object compiled before
  against the system's headers is taken over instead of compiled again. AppRun no longer asks for the
  system's Vulkan headers.
- #embed: tools/build_shaders.py also writes the font's bytes as runtime/ui/roboto_ttf.h (ignored by git,
  carried by a kit), and menu.cpp includes that where the compiler has no __has_embed. With #embed
  nothing changes.
- Before its long steps a Linux build that will compile now checks every runtime source that needs no
  header of the install with the compile step's own command (clang -fsyntax-only) and links an empty
  program against the game's libraries (make_game.py preflight): an old compiler, an old header or a
  missing development package stops it in the first seconds with the compiler's own errors. A failed
  compile names every file that failed, not only the three whose errors it prints.

Checked on Windows: tests/launcher_test.py passes with four new tests (117); the header of bytes and
#embed give the same 162,588 bytes, and both branches of menu.cpp compile; the early check passes on the
Windows file set with clang 23 and stops on files made to fail; the 34 local headers of release 363 are
the upstream tag's files by git blob id. Not run: anything on Linux, the pinned archive's download, a
compiler older than 23.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 12:42:17 -05:00
chrg6
63c7d3cb74 Make the frame limit count the frames the game draws, with frame generation on top.
The limit counted the frames on screen, so the game was paced at the limit over frame generation's multiplier: 60 at 4x drew 15 frames a second, and each step up of the multiplier took drawn frames away. Now a limit of 60 at 4x is 60 drawn and 240 shown. Only the display choice still counts the screen's frames, since a frame past the monitor's refresh is never seen, and with no limit set the default is 60 drawn rather than the monitor's rate.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 11:48:24 -05:00
chrg6
cdaab81727 Merge the texture detail setting into the combined test line.
One conflict, in the build kit's notes, which keep both the shader cache item and the Texture detail text.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 09:51:33 -05:00
chrg6
364a40ae9b Add the Texture detail row to the settings menu's Screen tab, in the four menu languages.
The row offers Automatic, Full, Reduced and Low, names the level Automatic picks on this card, and notes that a change takes effect after a restart.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 09:38:08 -05:00
chrg6
1f2b320736 Say "after restart" in the menu only while a restart would change something, and count the waiting settings on Restart.
A setting read at the start showed "after restart" under its value always. The values the game started with are now
kept (cfg.c start_values), a row shows the note only while its value differs from them, and the Restart button counts
the settings that wait, the upscaler, frame generation and HDR among them. A value the environment sets never waits.
--probe-cfg checks the count.

From pull request #377 by mooki000. Three conflicts in the menu resolved against the frame generation rows that came
in since: the upscaling row keeps its description by what can load, and the frame generation restart test knows both
ways NVIDIA's runs.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 09:14:23 -05:00
chrg6
a1fcbb0b07 Name NVIDIA's Shader Cache Size limit when the library warm finds the driver's cache cold.
The driver keeps the prepared shaders' libraries in its shader cache, and under Windows that cache has a size limit
set in NVIDIA's settings. A limit smaller than the set drops the oldest libraries as new ones come in (a 2048 MB cache
could not hold them on the reporter's RTX 5070 Ti), so every start builds them again in the background and play
stutters until that is done. The cold lines of the library warm now say so on NVIDIA under Windows, most plainly when
the cache is cold right after a warm that finished on the same driver; the build kit's notes and the example file say
it too. tests/warm_hint_check.py checks which hint each case gets.

From pull request #403 by tarballz; two conflicts resolved (an include and the build kit's notes).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 09:13:35 -05:00
chrg6
75b0dca907 Add Steam Deck button prompts beside the Xbox and PlayStation ones (prompts = steamdeck).
The Controls tab's Button prompts row and nx2navi.ini take steamdeck: the Deck's own labels in the game's
text, action prompt and ocarina notes, which are A B X Y where an Xbox pad has them, L1 R1 L2 R2 and L3 R3,
Menu and View. The style is drawn by tools/pad_prompts.py like the others, into romfs-patches-steamdeck
and romfs-patches-steamdeck-label, and every build with button art writes them.

Pull request #107 by loteran asked for PlayStation and Steam Deck prompts; it was closed, and the tree
took the PlayStation half in another form, drawn from button art instead of redrawn font glyphs. This is
the Steam Deck half in that form, written here: the art has no Deck style, so its entry in
tools/prompt_art.py names the Xbox pictures for the face buttons, Menu, View, the d-pad and the sticks
and the PlayStation pictures for the shoulders and stick clicks. It needs four pictures of the Xbox set
that no other style used (the white letters on a light button, for the ocarina's notes); a release made
with --prompt-art copies them with the rest.

The Windows files compile and tests/launcher_test.py passes, with the prompts step's test following the
number of folders (seven now); every picture the style names is in the art used for releases. The style
was not drawn here (that needs the game's font), and --probe-cfg, which has a new check, was not run.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 06:53:30 -05:00
chrg6
eb2608296b Generate frames with DLSS on Linux through NGX itself: 2x to 6x or dynamic, switched while the game runs.
DLSS Frame Generation went through Streamline, which only the Windows build has, so framegen = dlss did
nothing on Linux. There it now calls NGX's frame generation feature directly (vk_dlaa.c vk_ngxfg_*), with
the depth, motion vectors, jitter, camera and HUD-less picture the Streamline path gets. With it on, each
frame is composed into an image of its own instead of a swapchain image, the generated frames are made at
the end of the frame's command buffer, and a presenter thread shows them and then the rendered frame, each
copied into a swapchain image it acquires itself, spaced over the time between rendered frames
(gfx_present.c ng_*). fg_mult is the multiplier: 2 to 6, or dynamic for only the frames the display's
refresh rate needs. NVIDIA's libnvidia-ngx-dlssg.so.<version> goes beside the game; it is not in the
repository or any kit.

Pull request #124 by spidercraft. Its base was the present path of 0.5, so it is ported by hand:
- taken as written: the NGX calls and the presenter (the block is lifted from the pull request's diff);
- wired again into present_image, create_swapchain, the output rate and the HUD-less picture as they
  are now, with every new branch behind vk_ngxfg_wanted, which is 0 in the Windows build: the code is
  compiled there and never reached;
- added: frame generation's feature and parameters are released before NGX shuts down, the presenter's
  images, semaphores and command pool go when the device is released and are made again for a new one,
  and a failed submit on this path settles its fence without an acquire semaphore;
- the menu: the Frame generation row follows the setting at once on Linux and says "after restart" only
  when the game started without DLSS upscaling; the multiplier row lists what the GPU allows and Dynamic;
- the overlay row of the pull request it builds on (#123) is not part of this.
It touches vk_dlaa.c, which another session is changing: one field in N, the FG state after it, one FN
line, the instance extension query, the capability read in vk_dlaa_init, the release, and a block appended
at the end.

Known gap, as in the pull request: on this path fps_limit counts the frames the game draws. The Windows
files compile without new warnings; nothing was run, and the Linux path is not compiled here.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 06:45:59 -05:00
chrg6
7d3185fca6 Let an AppImage without NVIDIA's DLSS library download it beside the game on machines with NVIDIA's driver.
An AppImage made with --no-dlss carries no NVIDIA file, and its builds had no DLSS. After a build its
AppRun now downloads the pinned libnvidia-ngx-dlss.so.<version> from NVIDIA's DLSS SDK repository when
/proc/driver/nvidia exists and the game folder holds no such library, checks its SHA-256 and writes the
notice that names its licence. NX2NAVI_NO_DLSS=1 skips it, and a failed download is a warning.

Pull request #290 by loteran, written again on top of the pin that came with the bundled library:
- the version, file name, checksum and address come from tools/dlss_pin.py when the AppImage is made,
  instead of a second copy in the script, and the notice is dlss_pin's own text, carried in the kit;
- it runs only when no library is beside the game, so it adds nothing to a build that placed the kit's
  and leaves a player's own version alone;
- the download goes to a name that does not start like the library's: the game takes any file with that
  prefix, and an interrupted download would have been one.

The generated AppRun passes bash -n; nothing was run on Linux here.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 06:38:45 -05:00
chrg6
32ee021905 Include NVIDIA's pinned DLSS library for Linux in the AppImage, and place it beside the built game.
On Linux the game looks for libnvidia-ngx-dlss.so.<version> beside the executable, and the driver brings
only NGX's core, so the DLSS modes did nothing in every Linux build. tools/dlss_pin.py pins the Linux
library of the DLSS version it pins for Windows, with its own size and SHA-256; tools/make_appimage.py
puts it in the kit's bin/ with its notice (downloaded from NVIDIA's DLSS SDK repository and checked, or
--dlss; --no-dlss leaves it out), and tools/make_game.py copies it beside the game on Linux as it copies
the DLL on Windows, keeping a library the player put there.

Pull request #297 by mooki000, taken as it is; the builder's description is merged with the button art
option that came with the release workflow. On Windows place_dlss does what it did. No NVIDIA file is in
the repository.

tests/launcher_test.py passes with its two new tests (112 tests, Python 3.12 on Windows); the AppImage
was not built here.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 06:38:02 -05:00
chrg6
d985e0ecb0 Offer FSR 3.1 in the Screen tab only where its library can load.
On Linux the Screen tab listed the five FSR 3.1 upscaling modes and AMD's frame generation, but only the
Windows build loads amd_fidelityfx_vk.dll: a player who picked a mode got the scene at the mode's share of
the picture with nothing to scale it back up. vk_ffx_available answers from the load's result once it ran,
and before that from the library file being there, so the menu can ask every frame. Where it says no,
the menu lists neither, a mode that is still set shows "in use: off", the scene keeps the game's own
share (vk_dlaa_fsr3), frame generation keeps the plain swapchain with one log line, and the texts name
what does run.

Pull request #371 by mooki000, taken as it is; two hunks merged by hand (vk_fg_wanted, which now also
stays off after a failure, and a README line that had gained Texture filtering). It touches vk_dlaa.c,
which another session is changing: vk_dlaa_fsr3 gains the availability test and one log text changes.

On Windows the menu changes only where the DLL is missing. The Windows files compile; nothing was run.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 06:33:22 -05:00
chrg6
f740e95a62 Fix the black 3D world under shadow storage on Linux and Proton, and copy only what each side wrote.
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>
2026-11-10 06:24:18 -05:00
chrg6
b2c5052ff5 Give Linux a memory limit that the low-memory handling can read.
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>
2026-11-10 06:20:03 -05:00
chrg6
c117cd8999 Set NVIDIA's shader disk cache on Linux only with NVIDIA's driver, in a folder that could be made and written, before any thread of the game starts.
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>
2026-11-10 06:17:13 -05:00
chrg6
32901bb337 Build the runtime's shader headers before make_game.py compiles, so a source checkout builds again.
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>
2026-11-10 06:16:58 -05:00
Michael Schienbein
c9942578e1 Keep NVIDIA's shader disk cache beside the exe with a limit that holds the prepared set.
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>
2026-11-10 01:56:31 -05:00
Michael Schienbein
03cb703e8f Apply anisotropic filtering while the game runs, and add it to the Screen tab.
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>
2026-11-10 01:56:30 -05:00
chrg6
cb234fade1 With frame generation and no frame limit chosen, limit to the display's refresh rate.
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>
2026-11-09 12:18:17 -05:00
itaskaev2
7341eec727 Download the pinned Streamline runtime for DLSS Frame Generation, and let the build kit carry it too.
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>
2026-11-09 12:18:17 -05:00
chrg6
c745a9ef2f Offer DLSS multi frame generation at 2x, 3x and 4x.
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>
2026-11-09 12:18:17 -05:00
chrg6
dfc5b90b59 Fix what the 0.6.0 review found, and ship DLSS Frame Generation in the launcher release.
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>
2026-11-09 09:10:36 -05:00
chrg6
7530ca5044 Build on Linux without libunwind, and ship build_game.py in the kit for the Linux build's VMA header.
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>
2026-11-09 08:22:06 -05:00
loteran
f3daf7e2b4 Build Ocarina natively on Linux with tools/make_game.py, and pack the kit as an AppImage.
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>
2026-11-09 08:21:36 -05:00
chrg6
8e30ad7927 Carry the button art in releases and draw the button prompts during the game build.
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>
2026-11-09 08:09:26 -05:00
chrg6
ef08fed45f Ship NVIDIA's DLSS library in the setup and the build kit, and put it beside the game.
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>
2026-11-09 08:09:26 -05:00
tangerineturtle1
9e574f84ec Replace the enhanced HDR with a port of RenoDX's tone mapper.
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>
2026-11-09 06:06:33 -05:00
chrg6
8daedbd806 Redraw the in-game menu with the game's own interface textures, and bring in the launcher's save import and faster builds.
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>
2026-11-08 03:57:01 -05:00
Claude
c403547c8b Add the NX2navi launcher: a one-click setup that builds the game from the player's dump, starts it and keeps it up to date.
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>
2026-11-07 14:50:52 -05:00
chrg6
ab5450da15 Describe DLSS, FSR 3.1, frame generation, HDR and the build kit in the READMEs; name a null call's caller.
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>
2026-11-07 13:31:07 -05:00
chrg6
fc6d84aa63 Prepare every one of the game's shaders when the build kit builds the game, so play does not stop to compile them.
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>
2026-11-07 13:31:06 -05:00
chrg6
a6bc3d837b Add a build kit that builds the game on the player's PC from their own .nup dump or exefs/romfs folders.
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>
2026-11-07 09:26:51 -05:00