diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 46e8a33a..27b53c19 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -123,6 +123,7 @@ folder's `licenses/` and in each component's own folder. | zlib 1.4 (MSYS2 `mingw-w64-ucrt-x86_64-zlib`), static library | https://zlib.net, Copyright (C) Jean-loup Gailly and Mark Adler | zlib | linked into `NX2navi-Setup.exe` (its unzip) | | mingw-w64 CRT startup code and winpthreads 14.0.0, GCC 16.2.0's `libgcc` (MSYS2 packages), static | https://www.mingw-w64.org, https://gcc.gnu.org | mingw-w64: ZPL-2.1 / MIT / public domain; libgcc: GPL-3.0 with the GCC Runtime Library Exception | linked into `NX2navi.exe` and `NX2navi-Setup.exe` | | NVIDIA DLSS 310.9.1 (`nvngx_dlss.dll`, NVIDIA's own build, unmodified; pinned with its size and sha256 in `tools/dlss_pin.py`) | https://github.com/NVIDIA/DLSS @ `v310.9.1` (`lib/Windows_x86_64/rel/nvngx_dlss.dll`), Copyright NVIDIA Corporation | NVIDIA RTX SDKs License (https://github.com/NVIDIA/DLSS/blob/v310.9.1/LICENSE.txt); licensed for systems with NVIDIA GPUs only | `app//bin/` with `nvngx_dlss-NOTICE.txt`, also in the build kit's `bin/`; `tools/make_game.py` copies both beside the built game, where NGX loads the library (`runtime/vk_dlaa.c`) | +| NVIDIA DLSS 310.9.1 for Linux (`libnvidia-ngx-dlss.so.310.9.1`, NVIDIA's own build, unmodified; pinned with its size and sha256 in `tools/dlss_pin.py`) | https://github.com/NVIDIA/DLSS @ `v310.9.1` (`lib/Linux_x86_64/rel/libnvidia-ngx-dlss.so.310.9.1`), Copyright NVIDIA Corporation | NVIDIA RTX SDKs License (https://github.com/NVIDIA/DLSS/blob/v310.9.1/LICENSE.txt), the one licence of the repository's Windows and Linux libraries; licensed for systems with NVIDIA GPUs only | the Linux build kit's `bin/` (the AppImage of `tools/make_appimage.py`, unless made with `--no-dlss`) with `libnvidia-ngx-dlss-NOTICE.txt`; `tools/make_game.py` copies both beside the built game, where NGX loads the library (`runtime/vk_dlaa.c`) | | NVIDIA Streamline 2.14.16 runtime (`sl.interposer.dll`, `sl.common.dll`, `sl.dlss_g.dll`, `sl.pcl.dll`, `sl.reflex.dll`, NVIDIA's own builds, unmodified) and NVIDIA DLSS Frame Generation 310.9.16 (`nvngx_dlssg.dll`, as the Streamline SDK ships it) | https://github.com/NVIDIA-RTX/Streamline, SDK release 2.14.16 (`bin/x64/`), Copyright NVIDIA Corporation | Streamline: MIT; `nvngx_dlssg.dll`: NVIDIA RTX SDKs License, licensed for systems with NVIDIA GPUs only | `app//bin/dlssg/` with both licence texts and `dlssg-NOTICE.txt` (each file's sha256), when the release is made with `tools/make_launcher.py --dlssg`, and the build kit's `bin/dlssg/` with `tools/make_kit.py --dlssg` (the SDK release pinned with its size and sha256 and each file's sha256 in `tools/streamline_pin.py`); `tools/make_game.py` copies them beside the built game, where `nx2navi_dlssg.dll` (this repository's `runtime/dlssg/`, built with the Streamline SDK's MIT headers) loads them (`runtime/vk_dlssg.c`) | | Button prompt art: the 128x128 PNGs of the Input Prompts Pack V7 (`XGamepad`, `P5Gamepad` and `Keyboard_Mouse` styles), unmodified | the pack as licensed to the repository owner; never in this repository | the pack's licence, which allows redistributing these pictures in NX2navi's releases (confirmed by the repository owner, 2026-11-09) | `app//prompt-art/` and the build kit's `prompt-art/` when made with `--prompt-art` (`tools/prompt_art.py`); `tools/pad_prompts.py` draws the game's Xbox, PlayStation and keyboard and mouse prompts from them on the player's PC | diff --git a/docs/README-BuildKit.md b/docs/README-BuildKit.md index 1a54f9ae..4a46042c 100644 --- a/docs/README-BuildKit.md +++ b/docs/README-BuildKit.md @@ -114,6 +114,15 @@ AMD FSR 3.1 loads AMD's `amd_fidelityfx_vk.dll`, which only the Windows build ca FSR 3.1 upscaling nor FSR 3.1 frame generation, and `taa = fsr3` or `framegen = fsr3` in `nx2navi.ini` leave the game's own scaling and swapchain as they are (the log says so). +NVIDIA DLSS (RTX cards) needs the driver's own `libnvidia-ngx.so.1` on the library path (the proprietary driver's +utilities package, `nvidia-utils` on Arch) and NVIDIA's DLSS library `libnvidia-ngx-dlss.so.` beside +`ocarina`. The AppImage's kit has the library in `bin/` with `libnvidia-ngx-dlss-NOTICE.txt`, which names its licence, +and the build copies both beside `ocarina` (`tools/make_appimage.py --no-dlss` leaves it out). From a source checkout, +or to use another DLSS version, put the library there yourself: NVIDIA publishes it as +`lib/Linux_x86_64/rel/libnvidia-ngx-dlss.so.` in [its DLSS repository](https://github.com/NVIDIA/DLSS), tagged +`v`, and `tools/dlss_pin.py` names the version the kit carries. Later builds keep a library they did not +place. Without it DLSS does nothing on Linux. + `tools/make_appimage.py` packs this kit into one `NX2navi-Linux--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` @@ -138,7 +147,8 @@ beside the game. `py -I tools\make_game.py --rebuild --out "C:\Games\Ocarina"` to draw them again (it skips everything else). - The build copies NVIDIA's DLSS library `nvngx_dlss.dll` (with `nvngx_dlss-NOTICE.txt`, which names its licence) and FSR 3.1's `amd_fidelityfx_vk.dll` beside `ocarina.exe` from the kit's `bin\`. To use another DLSS version, put - your own `nvngx_dlss.dll` there: later builds keep it. + your own `nvngx_dlss.dll` there: later builds keep it. On Linux the library is `libnvidia-ngx-dlss.so.` + (see "Linux"). - A kit with `bin\dlssg\` (made with `tools/make_kit.py --dlssg`) also brings DLSS Frame Generation on GeForce RTX 40 and 50 cards: the build copies `nx2navi_dlssg.dll`, NVIDIA Streamline's runtime, `nvngx_dlssg.dll` and their licence texts beside `ocarina.exe`, and Frame generation in the Screen menu then offers DLSS too. diff --git a/tests/launcher_test.py b/tests/launcher_test.py index f418cac5..37e0ff6d 100644 --- a/tests/launcher_test.py +++ b/tests/launcher_test.py @@ -1712,6 +1712,119 @@ class MakeGameTest(Tmp): self.assertEqual(self.mg.notice_sha256(self.tmp / 'n.txt'), dlss_pin.SHA256) self.assertEqual((dlss_pin.NAME, dlss_pin.NOTICE), (self.mg.DLSS_DLL, self.mg.DLSS_NOTICE)) + def test_dlss_library_beside_the_game_on_linux(self): + """The kit's libnvidia-ngx-dlss.so. and its notice go beside the game. The name carries the DLSS + version, so a kit with another version replaces the file an earlier build placed under its old name, and a + library the player put there, of any version, is kept. Each platform places only its own library.""" + from unittest import mock + kit, out = self.tmp / 'kit', self.tmp / 'game' + (kit / 'bin').mkdir(parents=True) + out.mkdir() + note = out / 'libnvidia-ngx-dlss-NOTICE.txt' + + def kit_lib(version, data, when): + for old in (kit / 'bin').glob('libnvidia-ngx-dlss.so.*'): + old.unlink() + lib = kit / 'bin' / f'libnvidia-ngx-dlss.so.{version}' + lib.write_bytes(data) + os.utime(lib, (when, when)) + (kit / 'bin' / 'libnvidia-ngx-dlss-NOTICE.txt').write_text( + f'NVIDIA DLSS {version}\n\nsha256 {hashlib.sha256(data).hexdigest()}\n') + + def beside(folder=out): + return sorted(p.name for p in folder.iterdir()) + + with mock.patch.object(self.mg, 'KIT', kit): + self.assertIsNone(self.mg.place_dlss(out, linux=True)) # a kit without DLSS: nothing + self.assertEqual(beside(), []) + (kit / 'bin' / 'nvngx_dlss.dll').write_bytes(b'the Windows DLL') + self.assertIsNone(self.mg.place_dlss(out, linux=True)) # the Windows DLL is not a Linux build's + self.assertEqual(beside(), []) + kit_lib('310.9.1', b'dlss one', 1_700_000_000) + self.assertIsNone(self.mg.place_dlss(out, linux=True)) + self.assertEqual(beside(), ['libnvidia-ngx-dlss-NOTICE.txt', 'libnvidia-ngx-dlss.so.310.9.1']) + self.assertEqual((out / 'libnvidia-ngx-dlss.so.310.9.1').read_bytes(), b'dlss one') + self.assertIn(hashlib.sha256(b'dlss one').hexdigest(), note.read_text()) + self.assertIsNone(self.mg.place_dlss(out, linux=True)) # unchanged + self.assertEqual(len(beside()), 2) + kit_lib('310.10.0', b'dlss two', 1_700_100_000) # another version: the earlier build's file goes + self.assertIsNone(self.mg.place_dlss(out, linux=True)) + self.assertEqual(beside(), ['libnvidia-ngx-dlss-NOTICE.txt', 'libnvidia-ngx-dlss.so.310.10.0']) + self.assertEqual((out / 'libnvidia-ngx-dlss.so.310.10.0').read_bytes(), b'dlss two') + self.assertIn(hashlib.sha256(b'dlss two').hexdigest(), note.read_text()) + # a library the player put there first, of another version: kept, and the kit's is not placed beside it + mine = self.tmp / 'mine' + mine.mkdir() + (mine / 'libnvidia-ngx-dlss.so.310.4.0').write_bytes(b"the player's own DLSS") + self.assertIn('libnvidia-ngx-dlss.so.310.4.0 kept', self.mg.place_dlss(mine, linux=True)) + self.assertEqual(beside(mine), ['libnvidia-ngx-dlss.so.310.4.0']) + # the same version with other bytes (the player's own build) is kept too + (mine / 'libnvidia-ngx-dlss.so.310.4.0').unlink() + (mine / 'libnvidia-ngx-dlss.so.310.10.0').write_bytes(b"the player's own build") + self.assertIn('kept', self.mg.place_dlss(mine, linux=True)) + self.assertEqual((mine / 'libnvidia-ngx-dlss.so.310.10.0').read_bytes(), b"the player's own build") + # a player's library put beside a library an earlier build placed: both stay + (out / 'libnvidia-ngx-dlss.so.310.4.0').write_bytes(b"the player's own DLSS") + self.assertIn('kept', self.mg.place_dlss(out, linux=True)) + self.assertEqual(len(beside()), 3) + # and a Windows build takes the DLL, not the library + win = self.tmp / 'win' + win.mkdir() + self.assertIsNone(self.mg.place_dlss(win)) + self.assertEqual(beside(win), ['nvngx_dlss.dll']) + + def test_dlss_pin_for_linux(self): + """The Linux library is pinned beside the Windows DLL, at the same DLSS version with its own size and sha256. + A file on disk must be the pinned one, a missing one is downloaded into the cache under its own name, and the + notice names the library and carries the sha256 line make_game.py reads.""" + import dlss_pin + from unittest import mock + p = dlss_pin.pin(linux=True) + self.assertEqual(p.name, f'libnvidia-ngx-dlss.so.{dlss_pin.VERSION}') + self.assertTrue(p.name.startswith(self.mg.DLSS_SO_PREFIX)) + self.assertFalse(p.notice.startswith(self.mg.DLSS_SO_PREFIX)) # the runtime takes that prefix for the library + self.assertEqual(p.notice, self.mg.DLSS_SO_NOTICE) + self.assertEqual(dlss_pin.pin().name, dlss_pin.NAME) # the default is still the Windows DLL + self.assertNotEqual((p.size, p.sha256), (dlss_pin.SIZE, dlss_pin.SHA256)) + (self.tmp / 'n.txt').write_text(dlss_pin.notice_text(linux=True), encoding='utf-8') + self.assertEqual(self.mg.notice_sha256(self.tmp / 'n.txt'), p.sha256) + self.assertIn(p.name, dlss_pin.notice_text(linux=True)) + self.assertNotIn('nvngx_dlss.dll', dlss_pin.notice_text(linux=True)) + data = b'a DLSS library' + lib, cache = self.tmp / 'file.so', self.tmp / 'dl' + lib.write_bytes(data) + fetched = [] + + def download(url, dest, sha256=None, size=None): + fetched.append((url, Path(dest).name, sha256, size)) + Path(dest).parent.mkdir(parents=True, exist_ok=True) + Path(dest).write_bytes(data) + return dest + + pinned = dict(LINUX_SIZE=len(data), LINUX_SHA256=hashlib.sha256(data).hexdigest(), + SIZE=len(data) + 1, SHA256=hashlib.sha256(data + b'!').hexdigest()) + with mock.patch.multiple(dlss_pin, **pinned), mock.patch('launcher.net.download', download), \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(dlss_pin.resolve(lib, cache, linux=True), lib) + with self.assertRaises(SystemExit): # the Windows DLL's pin does not accept the Linux file + dlss_pin.resolve(lib, cache) + lib.write_bytes(b'another DLSS!') + with self.assertRaises(SystemExit): + dlss_pin.resolve(lib, cache, linux=True) + with self.assertRaises(SystemExit): + dlss_pin.resolve(self.tmp / 'missing.so', cache, linux=True) + self.assertEqual(fetched, []) + self.assertEqual(dlss_pin.resolve(None, cache, linux=True), cache / p.name) + self.assertEqual(fetched, [(p.url, p.name, hashlib.sha256(data).hexdigest(), len(data))]) + self.assertEqual(dlss_pin.resolve(None, cache), cache / f'nvngx_dlss-{dlss_pin.VERSION}.dll') + self.assertEqual(fetched[1][:2], (dlss_pin.URL, f'nvngx_dlss-{dlss_pin.VERSION}.dll')) + bin_dir = self.tmp / 'bin' + bin_dir.mkdir() + dlss_pin.place(cache / p.name, bin_dir, linux=True) + self.assertEqual((bin_dir / p.notice).read_text(encoding='utf-8'), dlss_pin.notice_text(linux=True)) + self.assertEqual(sorted(x.name for x in bin_dir.iterdir()), sorted([p.notice, p.name])) + self.assertEqual((bin_dir / p.name).read_bytes(), data) + def test_compile_key_ignores_file_time_and_sysroot(self): """The compiler's id leaves its file time out (a tool update that puts the same compiler back keeps every object), and the key leaves --sysroot out (the fast compiler's objects are the UCRT64 compiler's).""" diff --git a/tools/dlss_pin.py b/tools/dlss_pin.py index ba3e758d..1451e065 100644 --- a/tools/dlss_pin.py +++ b/tools/dlss_pin.py @@ -1,12 +1,15 @@ -"""NVIDIA's DLSS library (nvngx_dlss.dll) for the releases: tools/make_launcher.py and tools/make_kit.py put it in the -release's bin/ beside AMD's FSR 3.1 library, and tools/make_game.py copies it beside the game, where NGX loads it -(runtime/vk_dlaa.c). Without it DLSS runs only where the NVIDIA driver supplies the model itself. +"""NVIDIA's DLSS library for the releases: nvngx_dlss.dll for Windows, libnvidia-ngx-dlss.so. for Linux. +tools/make_launcher.py and tools/make_kit.py put the DLL in the release's bin/ beside AMD's FSR 3.1 library, +tools/make_appimage.py puts the Linux library in the AppImage's kit bin/, and tools/make_game.py copies either beside +the game, where NGX loads it (runtime/vk_dlaa.c). Without it DLSS runs only where the NVIDIA driver supplies the model +itself (Windows), and on Linux not at all (runtime/resolution.c dlss_dll). The file is NVIDIA's own build from the DLSS SDK repository at a pinned tag, unmodified and checked against its size and SHA-256. NVIDIA's RTX SDKs License lets an application ship it in object form; the notice written beside it names -the version and the licence that covers it. +the version and the licence that covers it. Both platforms' files are published under the one licence file of that tag. """ from pathlib import Path +from typing import NamedTuple NAME = 'nvngx_dlss.dll' NOTICE = 'nvngx_dlss-NOTICE.txt' # beside the DLL; make_game.py reads its sha256 line @@ -14,18 +17,41 @@ VERSION = '310.9.1' URL = f'https://raw.githubusercontent.com/NVIDIA/DLSS/v{VERSION}/lib/Windows_x86_64/rel/{NAME}' SIZE = 58956912 SHA256 = '3975567b8943c53acce397f2b72380092f84f162d00b0d2c7d08a1025c563983' +# The Linux library of the same release. Its name ends in the DLSS version; NGX finds it by that name beside the +# executable (runtime/resolution.c dlss_dll looks for the prefix). +LINUX_NAME = f'libnvidia-ngx-dlss.so.{VERSION}' +LINUX_NOTICE = 'libnvidia-ngx-dlss-NOTICE.txt' # beside the library; make_game.py reads its sha256 line +LINUX_URL = f'https://raw.githubusercontent.com/NVIDIA/DLSS/v{VERSION}/lib/Linux_x86_64/rel/{LINUX_NAME}' +LINUX_SIZE = 59481944 +LINUX_SHA256 = '7561f16cab74e2b7ccf791bf44bb9cc43abb44bcf78f33c5663520ad4e857e02' LICENSE_URL = f'https://github.com/NVIDIA/DLSS/blob/v{VERSION}/LICENSE.txt' -def notice_text(): - return (f'{NAME} is NVIDIA DLSS {VERSION}, unmodified, as published in NVIDIA\'s DLSS SDK\n' +class Pin(NamedTuple): + name: str + notice: str + url: str + size: int + sha256: str + + +def pin(linux=False): + """The pinned file of a platform.""" + if linux: + return Pin(LINUX_NAME, LINUX_NOTICE, LINUX_URL, LINUX_SIZE, LINUX_SHA256) + return Pin(NAME, NOTICE, URL, SIZE, SHA256) + + +def notice_text(linux=False): + p = pin(linux) + return (f'{p.name} is NVIDIA DLSS {VERSION}, unmodified, as published in NVIDIA\'s DLSS SDK\n' f'(https://github.com/NVIDIA/DLSS, tag v{VERSION}). Copyright NVIDIA Corporation.\n' '\n' 'It is not part of NX2navi and NX2navi\'s licence does not cover it. It is distributed under the\n' f'NVIDIA RTX SDKs License, and its use is subject to that licence: {LICENSE_URL}\n' 'NVIDIA licenses DLSS only for use on systems with NVIDIA GPUs.\n' '\n' - f'sha256 {SHA256}\n') + f'sha256 {p.sha256}\n') def sha256_file(path): @@ -37,30 +63,31 @@ def sha256_file(path): return h.hexdigest() -def resolve(local, cache_dir): - """The pinned DLL: local when given (it must be the pinned file), else the copy in cache_dir, downloaded when - missing (launcher/net.py: resumed, checked against the size and SHA-256).""" +def resolve(local, cache_dir, linux=False): + """The pinned library: local when given (it must be the pinned file), else the copy in cache_dir, downloaded when + missing (launcher/net.py: resumed, checked against the size and SHA-256). linux: the Linux library, else the DLL.""" + p = pin(linux) if local: local = Path(local) if not local.is_file(): raise SystemExit(f'{local} does not exist') - if local.stat().st_size != SIZE or sha256_file(local) != SHA256: + if local.stat().st_size != p.size or sha256_file(local) != p.sha256: raise SystemExit(f'{local} is not NVIDIA DLSS {VERSION} (size or SHA-256 differ); a newer DLSS needs ' 'VERSION, SIZE and SHA256 in tools/dlss_pin.py changed first') return local import sys sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from launcher import net - dest = Path(cache_dir) / f'nvngx_dlss-{VERSION}.dll' - if not (dest.is_file() and sha256_file(dest) == SHA256): - print(f'download {URL}', flush=True) - net.download(URL, dest, sha256=SHA256, size=SIZE) + dest = Path(cache_dir) / (p.name if linux else f'nvngx_dlss-{VERSION}.dll') + if not (dest.is_file() and sha256_file(dest) == p.sha256): + print(f'download {p.url}', flush=True) + net.download(p.url, dest, sha256=p.sha256, size=p.size) return dest -def place(src, bin_dir): - """The DLL and its notice into a release's bin/.""" +def place(src, bin_dir, linux=False): + """The library and its notice into a release's or the kit's bin/.""" import shutil - bin_dir = Path(bin_dir) - shutil.copy2(src, bin_dir / NAME) - (bin_dir / NOTICE).write_text(notice_text(), encoding='utf-8') + p, bin_dir = pin(linux), Path(bin_dir) + shutil.copy2(src, bin_dir / p.name) + (bin_dir / p.notice).write_text(notice_text(linux), encoding='utf-8') diff --git a/tools/make_appimage.py b/tools/make_appimage.py index 55d4d619..0debe26c 100644 --- a/tools/make_appimage.py +++ b/tools/make_appimage.py @@ -2,15 +2,17 @@ player's PC from their own dump with tools/make_game.py, as NX2navi-Setup.exe does on Windows. python3 tools/make_appimage.py --python --appimagetool [--out dist] + [--dlss libnvidia-ngx-dlss.so. | --no-dlss] The AppImage holds no game files: the kit's tools/, recomp/, runtime/ and docs, the shader translator built by -tools/build_xlate.py (bin/nx2navi_xlate), the pinned Vulkan Memory Allocator header, and a relocatable CPython 3.14 -(python-build-standalone, as `uv python install 3.14` puts in ~/.local/share/uv/python) with numpy, lz4, zstandard, -fonttools and pillow, and with --prompt-art the button art make_game.py's prompts step draws from (as make_kit.py ---prompt-art: the pictures tools/prompt_art.py art_files names, into the kit's prompt-art folder; without it that step -is skipped and the game keeps the Switch prompts). zbic, the GPL package that unpacks the game's program files, is not -bundled: the AppImage installs it with pip into ~/.local/share/nx2navi/pydeps the first time it runs, as the Windows -launcher downloads it. +tools/build_xlate.py (bin/nx2navi_xlate), NVIDIA's DLSS library with its notice (bin/, tools/dlss_pin.py: downloaded +from NVIDIA's DLSS SDK repository and checked, or --dlss; not with --no-dlss), the pinned Vulkan Memory Allocator +header, and a relocatable CPython 3.14 (python-build-standalone, as `uv python install 3.14` puts in +~/.local/share/uv/python) with numpy, lz4, zstandard, fonttools and pillow, and with --prompt-art the button art +make_game.py's prompts step draws from (as make_kit.py --prompt-art: the pictures tools/prompt_art.py art_files names, +into the kit's prompt-art folder; without it that step is skipped and the game keeps the Switch prompts). zbic, the GPL +package that unpacks the game's program files, is not bundled: the AppImage installs it with pip into +~/.local/share/nx2navi/pydeps the first time it runs, as the Windows launcher downloads it. The compiler and the libraries come from the system (README-BuildKit.md, "Linux"): the AppImage checks for clang, clang++, lld and the headers before it starts, and names the packages for Arch, Fedora and Debian/Ubuntu. @@ -134,6 +136,9 @@ def main(): ap.add_argument('--python', type=Path, required=True, help='a relocatable CPython 3.14 folder (python-build-standalone, ~/.local/share/uv/python/cpython-3.14*)') ap.add_argument('--appimagetool', type=Path, required=True, help='appimagetool-x86_64.AppImage') + ap.add_argument('--dlss', type=Path, help="NVIDIA's libnvidia-ngx-dlss.so. from disk instead of " + "downloading it from NVIDIA's DLSS SDK repository") + ap.add_argument('--no-dlss', action='store_true', help='leave NVIDIA DLSS out of the AppImage') ap.add_argument('--out', type=Path, default=ROOT / 'dist', help='where the AppImage goes (default dist/)') ap.add_argument('--prompt-art', type=Path, nargs='+', metavar='FOLDER', help='folders holding XGamepad, P5Gamepad and Keyboard_Mouse (or those folders): the button art, as ' @@ -146,12 +151,14 @@ def main(): a.python / 'bin' / 'python3'): if not need.exists(): sys.exit(f'{need} is missing (tools/build_xlate.py builds the translator; tools/build_game.py fetches VMA)') + sys.path.insert(0, str(ROOT / 'tools')) + import dlss_pin + dlss = None if a.no_dlss else dlss_pin.resolve(a.dlss, ROOT / 'build' / 'dl', linux=True) app = ROOT / 'build' / 'appimage' / 'NX2navi.AppDir' shutil.rmtree(app, ignore_errors=True) kit = app / 'usr' / 'kit' kit.mkdir(parents=True) - sys.path.insert(0, str(ROOT / 'tools')) import build_shaders build_shaders.build(ROOT / 'runtime') # the image carries the runtime's shaders compiled (runtime/*_spv.h) for d in KIT_DIRS: @@ -164,6 +171,8 @@ def main(): (kit / 'bin').mkdir() for f in ('nx2navi_xlate', 'nx2navi_xlate.id'): shutil.copy2(xlate / f, kit / 'bin' / f) + if dlss: # DLSS: NGX loads it from beside the game (runtime/vk_dlaa.c); make_game.py copies it there + dlss_pin.place(dlss, kit / 'bin', linux=True) (kit / 'toolchain' / 'vma').mkdir(parents=True) shutil.copy2(ROOT / 'toolchain' / 'vma' / 'vk_mem_alloc.h', kit / 'toolchain' / 'vma' / 'vk_mem_alloc.h') if a.prompt_art: diff --git a/tools/make_game.py b/tools/make_game.py index deaa1c69..1b5c2a8e 100644 --- a/tools/make_game.py +++ b/tools/make_game.py @@ -554,6 +554,9 @@ def copy_if_changed(src, dst): DLSS_DLL, DLSS_NOTICE = 'nvngx_dlss.dll', 'nvngx_dlss-NOTICE.txt' # tools/dlss_pin.py, in the kit's bin/ +# The Linux library's name ends in the DLSS version (tools/dlss_pin.py): it is found by its prefix, as the runtime +# finds it beside the game (runtime/resolution.c dlss_dll). +DLSS_SO_PREFIX, DLSS_SO_NOTICE = 'libnvidia-ngx-dlss.so.', 'libnvidia-ngx-dlss-NOTICE.txt' # DLSS Frame Generation (runtime/vk_dlssg.c) loads these from beside the game: nx2navi_dlssg.dll (runtime/dlssg/, # tools/build_dlssg.py), NVIDIA Streamline's interposer and the plugins it uses, and NGX's frame generation library. # A release carries them with their licence texts in bin/dlssg/ (tools/make_launcher.py --dlssg); without the whole set @@ -564,7 +567,7 @@ DLSSG_FILES = ('nx2navi_dlssg.dll', 'sl.interposer.dll', 'sl.common.dll', 'sl.dl def notice_sha256(path): - """The sha256 line of a DLSS notice: the DLL a kit placed beside the game.""" + """The sha256 line of a DLSS notice: the library a kit placed beside the game.""" try: for line in path.read_text(encoding='utf-8').splitlines(): if line.startswith('sha256 '): @@ -574,22 +577,35 @@ def notice_sha256(path): return None -def place_dlss(out): +def dlss_files(folder, linux): + """The DLSS libraries in folder, by name: nvngx_dlss.dll, or on Linux every libnvidia-ngx-dlss.so..""" + return sorted(p for p in folder.glob(DLSS_SO_PREFIX + '*' if linux else DLSS_DLL) if p.is_file()) + + +def place_dlss(out, linux=False): """NVIDIA's DLSS library from the kit's bin/ beside the game, where NGX loads it (runtime/vk_dlaa.c), with its - notice. A different nvngx_dlss.dll the player put there (another DLSS version) is kept; the one an earlier build - placed, named by the notice beside it, is replaced. Returns a line for the build's log when it kept the player's.""" - src, dst, note = KIT / 'bin' / DLSS_DLL, out / DLSS_DLL, KIT / 'bin' / DLSS_NOTICE - if not src.is_file(): + notice. A different library the player put there (another DLSS version) is kept; the one an earlier build placed, + named by the notice beside it, is replaced. linux: the Linux library, whose name carries the DLSS version, so a + kit with another version replaces the earlier build's file under its old name. Returns a line for the build's + log when it kept the player's.""" + note_name = DLSS_SO_NOTICE if linux else DLSS_NOTICE + srcs, note = dlss_files(KIT / 'bin', linux), KIT / 'bin' / note_name + if not srcs: return None - if dst.is_file(): + src = srcs[-1] + beside = dlss_files(out, linux) + for dst in beside: s, d = os.stat(src), os.stat(dst) - if d.st_size != s.st_size or int(d.st_mtime) != int(s.st_mtime): + if dst.name != src.name or d.st_size != s.st_size or int(d.st_mtime) != int(s.st_mtime): have = sha256_file(dst) - if have != notice_sha256(out / DLSS_NOTICE) and have != sha256_file(src): - return f"{DLSS_DLL} kept: the one already beside the game is not the kit's (another DLSS version)" - copy_if_changed(src, dst) - if note.is_file() and (not (out / DLSS_NOTICE).is_file() or (out / DLSS_NOTICE).read_bytes() != note.read_bytes()): - shutil.copy2(note, out / DLSS_NOTICE) # by content: two versions' notices can share size and second + if have != notice_sha256(out / note_name) and have != sha256_file(src): + return f"{dst.name} kept: the one already beside the game is not the kit's (another DLSS version)" + copy_if_changed(src, out / src.name) + for dst in beside: + if not dst.samefile(out / src.name): # another version's library, placed by an earlier build: replaced + dst.unlink() + if note.is_file() and (not (out / note_name).is_file() or (out / note_name).read_bytes() != note.read_bytes()): + shutil.copy2(note, out / note_name) # by content: two versions' notices can share size and second return None @@ -1331,7 +1347,7 @@ def step_compile(out, cache_dir, msys, jobs, romfs_job=None, update=False): copy_if_changed(xlate / name, out / name) if (KIT / 'bin' / 'amd_fidelityfx_vk.dll').is_file(): # FSR 3.1 (runtime/vk_ffx.cpp) copy_if_changed(KIT / 'bin' / 'amd_fidelityfx_vk.dll', out / 'amd_fidelityfx_vk.dll') - kept = place_dlss(out) if WIN else None # NVIDIA DLSS (runtime/vk_dlaa.c): its library is a Windows DLL + kept = place_dlss(out, linux=not WIN) # NVIDIA DLSS (runtime/vk_dlaa.c): this platform's library from the kit if kept: say(f' {kept}') if WIN and place_dlssg(out): # DLSS Frame Generation (runtime/vk_dlssg.c): Windows DLLs too