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>
This commit is contained in:
Michael Schienbein
2026-11-09 17:40:22 -06:00
committed by chrg6
parent 03cb703e8f
commit c9942578e1
4 changed files with 44 additions and 1 deletions

View File

@@ -96,6 +96,11 @@ processor can fill memory and swap until the kernel stops processes of the deskt
On NVIDIA, start the game with `NX2NAVI_SHADOW=0` if the 3D world stays black while the HUD draws (issue #72). The
profile, logs and settings are in the `--out` folder as on Windows.
On NVIDIA the driver keeps the shaders it compiled in `nvidia-cache` beside `ocarina`, not in `~/.cache/nvidia`, where
one run adds at most 1 GiB while the preparation at the end of the build makes about 1.5 GB. The game sets the folder
and a limit of 8 GiB itself (`__GL_SHADER_DISK_CACHE_PATH` and `__GL_SHADER_DISK_CACHE_SIZE` in the environment take
precedence). Deleting the folder costs one slow start; `./ocarina --warm-shaders` fills it again.
`tools/make_appimage.py` packs this kit into one `NX2navi-Linux-<version>-x86_64.AppImage` with its own Python, for
players without a source checkout: it checks for the packages above, installs zbic with pip on its first run, picks
`--jobs` from the memory, builds into `~/Games/Ocarina` (or the folder given after the dump) and writes `play.sh`

View File

@@ -209,6 +209,14 @@ The probes open a window, so from an SSH session they need the desktop session's
skipped: no graphics+compute+present queue family`: on Wayland `XDG_RUNTIME_DIR=/run/user/$(id -u)
WAYLAND_DISPLAY=wayland-1 SDL_VIDEODRIVER=wayland` (the socket name is in `$XDG_RUNTIME_DIR`), on X11 `DISPLAY=:0`.
NVIDIA's Linux driver compiles into a disk cache of its own. A process adds at most `__GL_SHADER_DISK_CACHE_SIZE` to it
(1 GiB by default; at a start the files after the first are deleted while the folder is over that size), and the default
folder, `~/.cache/nvidia`, is shared with every program. `--prepare-shaders` of game 1.1.1 builds 42,142 variants, about
1.5 GB: on the default limit about 30,700 of them were stored and the rest compiled again at their first draw in play.
`runtime/vk_device.c` therefore points `__GL_SHADER_DISK_CACHE_PATH` at `nvidia-cache` beside `ocarina` and the size at
8 GiB before the first Vulkan call, each unless the environment sets it, as it does `MESA_SHADER_CACHE_DIR` for RADV. The
folder can be deleted; `ocarina --warm-shaders` fills it again.
Memory: on Windows the GPU imports guest memory and `GetWriteWatch` tracks the CPU's writes to it. On Linux,
re-protecting imported memory invalidates the driver's userptr buffer on every submit (about 1.3 ms per 16 MiB on
amdgpu), and importing pins the pages, which drops their write protection. Each CPU-visible pool therefore gets a

View File

@@ -202,7 +202,9 @@ bin/ nx2navi_xlate, hactool, ninja, libSDL3.so.0
- The setup's temp folder is `.setup-temp` inside the install folder (`--temp` changes it): the AppImage is mounted
read-only, and a Steam Deck's `/home` is often too full for the generated C while the game goes to the SD card.
- The game keeps Mesa's shader cache in `mesa-cache` beside `nx2navi` unless `MESA_SHADER_CACHE_DIR` is set, for the
same reason: RADV fills its default folder in `~/.cache` up to 1 GB.
same reason: RADV fills its default folder in `~/.cache` up to 1 GB. NVIDIA's driver cache goes to `nvidia-cache`
beside it with a limit of 8 GiB unless `__GL_SHADER_DISK_CACHE_PATH` or `__GL_SHADER_DISK_CACHE_SIZE` is set: the
default limit of 1 GiB is smaller than what the shader preparation builds.
- The window follows `LANG` for English or Russian, cancels through SIGTERM to the CLI's process group (the CLI handles
it as Ctrl+Break on Windows) and opens the finished folder with `xdg-open`.
- Requirements beyond Windows': Linux 6.7 or later (the write watch, `docs/building.md`) and glibc 2.34.

View File

@@ -72,9 +72,37 @@ static void mesa_cache_beside_exe(void) {
plat_setenv("MESA_SHADER_CACHE_DIR", dir);
}
#ifdef __linux__
// NVIDIA's Linux driver keeps the pipelines it compiles in a disk cache of its own, by default in ~/.cache/nvidia,
// shared with every program. One process adds at most __GL_SHADER_DISK_CACHE_SIZE to it (1 GiB unless set), and 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 into it in one process: with the default limit it stored about 30,700 of them and the rest
// were compiled again by the first start after it. So unless the environment sets them, the cache goes to nvidia-cache
// beside the exe, which nothing else writes, with a limit of NV_CACHE_BYTES. The driver makes GLCache inside that
// folder but not the folder, so it is made here.
#define NV_CACHE_BYTES "8589934592" // 8 GiB
static void nvidia_cache_beside_exe(void) {
char dir[PLAT_PATH_MAX * 2], size[32];
if (!plat_getenv("__GL_SHADER_DISK_CACHE_PATH", dir, sizeof dir)) {
int len = plat_exe_path(dir, sizeof dir - 16);
char *cut = len ? plat_last_sep(dir) : NULL;
if (cut) {
strcpy(cut + 1, "nvidia-cache");
plat_fs_mkdir(dir); // already there is fine
plat_setenv("__GL_SHADER_DISK_CACHE_PATH", dir);
}
}
if (!plat_getenv("__GL_SHADER_DISK_CACHE_SIZE", size, sizeof size))
plat_setenv("__GL_SHADER_DISK_CACHE_SIZE", NV_CACHE_BYTES);
}
#endif
static int create_instance(void) {
vk_dlssg_early(); // DLSS Frame Generation: Streamline before any Vulkan call of the process (its slInit asks so)
mesa_cache_beside_exe();
#ifdef __linux__
nvidia_cache_beside_exe();
#endif
const char *ext[12];
uint32_t n_ext = (uint32_t)gfx_window_vk_extensions(ext, 7);
const char *layers[] = {"VK_LAYER_KHRONOS_validation"};