NVIDIA DLSS, DLAA and Reflex (NGX + Vulkan, Linux and Windows, opt-in) #841

Merged
hckrman101 merged 65 commits from dlss into main 2026-11-09 19:14:40 +00:00
hckrman101 commented 2026-11-08 22:56:16 +00:00 (Migrated from github.com)

NVIDIA DLSS for Hookshot: Super Resolution, DLAA and Reflex, through NVIDIA's NGX library and Vulkan directly (no Streamline), on Linux and Windows. Opt-in at build time and off by default: a normal build, CI's release builds and the Builder without its NVIDIA DLSS tick are unchanged and fetch nothing.

No Streamline: everything uses NVIDIA's NGX library from the DLSS SDK directly, plus Vulkan's own VK_NV_low_latency2 for Reflex, so it works on native Linux as well as Windows.

NVIDIA's files. Nothing of NVIDIA's is committed, bundled or uploaded. Only someone who opts in fetches them, for their own build: scripts/ngx_sdk.py downloads them from NVIDIA's own repository (NVIDIA/DLSS on GitHub) at one pinned commit, checks every file against a SHA-256 in the script, and refuses to download until NVIDIA's license is accepted (--accept-nvidia-license, or the Builder's tick, which shows the license first). The only automated fetch is the vulkan-dlss CI workflow, which downloads the headers and the .lib to check that the Windows link still works: it runs by hand (workflow_dispatch), and on a push or pull request only when a file DLSS's build depends on changes (paths: filters in its own workflow file, so the other jobs are untouched). It accepts NVIDIA's license for that CI run only, and uploads and caches nothing.

What's in it

Commit What
DLSS Super Resolution NGX in place of OoT's gameplay upscale network
Camera jitter sub-pixel jitter of the scene camera while DLSS draws
DLAA DLSS at the render size, as anti-aliasing only
Reflex NVIDIA Reflex latency mode and markers
Output resolutions (#849) DLSS follows the 2048x1152 / 2560x1440 / 3840x2160 outputs and the new Resolution row, down to a third of the output (Ultra Performance); a render size equal to the output runs as DLAA
(merges of main, a test fix, a Reflex setup-order fix)

DLSS Super Resolution

  • Upscale: NGX starts with the DLSS runtime (310.9.16) placed in the game folder's ngx/. DLSS then replaces OoT's gameplay upscale network (fd029a31) every frame, preset K. Input and output sizes follow the game's: with #849 the output is 2048x1152, 2560x1440 or 3840x2160 and the Resolution row scales the 3D against it; the feature is made again when the output size changes or the render size leaves its mode's range. Colours and brightness match the native path.
  • Motion vectors: reprojected from depth with the scene camera, read from vertex ubo 0 (Eden's cbuf 3). The buffer's "previous matrix" slot at 0x180 is all zeros in this game version, so the camera from the last frame DLSS drew is used instead. RX_VK_DLSS_DEBUG=motion shows them.
  • Fallback: any unexpected input (format, size, missing camera) falls back to the native network and logs the reason.

Camera jitter

nx2/oot_jitter.py patches the function that fills each view's uniform buffers (three loads of the view's projection) to add a Halton sub-pixel offset to P[0][2]/P[1][2] in registers, only while DLSS draws. Nothing in the game's memory changes: the GPU gets the jittered projection, and the CPU (shadow cascade fitting, culling) keeps the real camera. The host asks the renderer for the scene size and the sequence's length through rx_option (HOS_OPT_DLSS_JITTER_W, _H, _PHASES). The renderer reads the offset back from P[0][2]/P[1][2], passes it to DLSS and removes it from the motion vectors. Verified in game: the offsets go through the sequence as expected, with no faults.

The sequence is 8 × (output / render)² frames long, 8 (DLAA) to 64, as NVIDIA's guide recommends. The sites are only lifted into a build with NVIDIA's SDK (make_game.py sets OOT_DLSS_JITTER=1 for the lift, part of its cache key). dlss_jitter_clip (in dlss_math.h, tested) picks the projections that get it: perspective, at 16:9 or the window's ratio; orthographic and 1:1 projections (the shadow maps, the per-character projector) never do.

Shadows (reported by a tester): the first version put the jitter into sead's projection itself, so the shadow cascades, fitted to the view frustum, followed it and their origin moved by about 0.17 shadow texels a frame with a still camera at DLAA 3840x2160 (up to 0.4), against 0.003 with the jitter off. With the jitter only in the uniform buffers, it is back to the jitter-off level; picture detail is unchanged and fps is the same (38.5–39.4 at DLAA 3840x2160, jitter on or off).

DLAA

DLAA is the NVIDIA DLAA choice of the Upscaler row (RX_VK_DLSS=2; see Settings). DLAA runs DLSS at a scale of 1 on the scene as drawn. At full scale the game doesn't run its 4/3 upscale but the same network as a same-size pass (9dc98871eb7fc101); DLSS replaces that pass too, which also covers the Resolution row's top step with DLSS on. The 3D is held at full scale whatever the Resolution row says, and dlss_out.comp scales the result to the game's upscale output.

Reflex

A new NVIDIA Reflex row: Off / On / On + Boost (RX_VK_REFLEX=0/1/2).

  • Reflex is now zero-impact when Off: no latency mode on the swapchain, no markers, no sleep, no present ids and nothing logged. Turning the row on or off makes the swapchain again at the next present.
  • While it is on, the swapchain is made with latency mode on, and each frame the driver gets its markers, keyed by the present id: simulation start and input after vkLatencySleepNV, simulation end and render-submit start at the game's present, render-submit end before the present's submit, and present start/end around vkQueuePresentKHR.
  • The device's VK_NV_low_latency2 and VK_KHR_present_id are still enabled whenever the device has them, so the row works without a restart. Enabled alone they do nothing: nothing of them is used until the row is on. RX_VK_REFLEX=- leaves them off.
  • RX_VK_REFLEX_LOG=1 (opt-in) prints the driver's own timings (vkGetLatencyTimingsNV) every 300 frames while Reflex is on.

Status

  • DLSS SR runs in play; colours and brightness match native
  • Motion vectors from depth + camera
  • Camera jitter runs in game
  • DLAA and Reflex: committed, build, and the full test suite passes
  • Up to date with main, including #849's output resolutions
  • In game at all three outputs (no faults; VRAM fine at 4K): 2048x1152 from 1536x864, 2560x1440 from 1920x1080, 3840x2160 from 2880x1620
  • DLAA in game: at full scale the game runs its upscale network as a same-size pass (9dc98871), which DLSS now replaces too; NGX picks DLAA mode (2560x1440 → 2560x1440)
  • Quality vs native at a 2560x1440 output, without jitter so far: DLSS is softer and has no halos, while the native network sharpens hard (more texture detail, halos on edges). DLSS shimmers less on a still camera (mean frame-to-frame difference 2.7 vs 3.4), brightness is the same, and no ghosting was seen on Link or Saria while panning. Still to capture: with jitter, and DLAA. The softness may want the existing Sharpness option or one of its own.
  • fps: native vs DLSS vs DLAA, measured (see Measurements)
  • Reflex On / On + Boost vs Off latency, measured (see Measurements)
  • Skinned characters (Link, the other characters and animals) have their own motion vectors and no longer ghost (see Moving objects); particles, water, mist, vertex-animated foliage and Link's face and hair still use the camera's motion
  • Windows: built and tested on Windows 11 with an RTX 5080 (see the Windows checks)
  • A packaged way to get the runtime: the Builder's opt-in NVIDIA DLSS tick fetches it after the license is accepted (Windows); scripts/ngx_sdk.py elsewhere
  • DLSS Frame Generation is not in this PR. Newer NGX releases offer it on Linux too, so it can follow as its own change (a newer SDK pin, the feature's own inputs such as motion vectors and depth, and pacing against Hookshot's own frame generation).

Since the review

  • The barriers around NGX's evaluation are recorded again: vk_dlss.cpp now includes vk_work.h and counts NGX's own commands (note_work() after the feature's creation and the evaluation), so full_barrier's dedupe no longer leaves them out.
  • CI's builder workers test passes Runner.Info's new ngxSdk argument.
  • Reflex is now zero-impact when Off (see Reflex).
  • NGX's data folder is made with the error_code overload: a folder that cannot be made no longer ends the game. Windows paths are wide all the way.
  • NGX's parameters go through the SDK's C functions, not the C++ virtual methods (an ABI mismatch under llvm-mingw).
  • The jitter goes only into the views' uniform buffers (not the game's cameras, so the shadow cascades stay steady), is only lifted into builds with the SDK, and its sequence is as long as DLSS's scale wants.
  • Another network of the upscale's shape is replaced only when its input is the gameplay networks' B10G11R11, not only its output.
  • DLSS's feature, images and deferred frees are let go after 120 frames unused, so turning it off gives its video memory back.
  • descriptors_disturbed() on every way out once commands are recorded; failed and last_frame are atomics; make_pipelines no longer leaks; a 60 s timeout on each SDK download.
  • The investigation switches (RX_VK_DLSS_JITTER_SIGN, RX_VK_DLSS_SQUARE) and the camera dump are gone, stale comments fixed, the jitter's options named (HOS_OPT_DLSS_JITTER_*).
  • The camera math (reprojection, jitter, Halton) is in gpu/vk/dlss_math.h and tested on the CPU (tests/test_dlss_math.py); the menu's rules are tested by behaviour in test_upscaler_rows.cpp.
  • The vulkan-dlss CI job is in its own workflow: manual, or on DLSS's files only. ngx-sdk/ is git-ignored.
  • host/rx_backend.c now includes gpu/vk/dlss_math.h, so the host objects' cache key hashes it and the Linux AppImage carries it.
  • Merged with main again (016d582, with the start-up screen #860). Conflicts in vk_main.cpp, vk_replay.cpp, nx2/build.py and test_shader_stamp_cache.py, all resolved keeping both sides.
  • Merged with main before that (#939, #946 and the rest). One conflict, in vk_menu.cpp: #946 moved the Insert menu's value changes into set_row (keys, clicks, the wheel and slider drags all go through it), and the Upscaler's rules (rows that cannot change here stay as they are, the Upscaler and Render resolution step through upscaler_rows.h, another Upscaler snaps the Render resolution) moved into set_row with it, so the mouse follows them too.

Checked again on Linux after these changes (RTX 3090): builds with and without the SDK (without it: no NGX in hookshot_vk.so, no ngx folder, no jitter sites in the lifted code), the Windows hookshot_vk.dll cross-built with the SDK through llvm-mingw (imports only Windows' own DLLs and the UCRT), tests/run_all.py green, actionlint clean. In game: DLSS draws the upscale correctly, the jitter steps through its sequence, Reflex On runs with its markers and timings, and with Reflex Off the log has no Reflex line at all.

Moving objects

OoT has no velocity buffer: its scene pass writes a single B10G11R11 colour target plus depth, and its upscale network samples only the colour, so DLSS had only the camera's motion and anything that moves by itself ghosted.

Skinned draws (Link, the other characters and animals) are ordinary indexed vertex draws with a recognisable layout: position, bone weights and bone indices, with the bone palette in a uniform buffer. vk_draw.cpp picks those that draw into the scene, and at the upscale each is drawn again into the motion image, each vertex skinned with this frame's bones and the previous frame's. The vertex and index data are read from the game's own buffers, and the poses are copied on the GPU into two buffers that take turns, so nothing is read back to the CPU. A pixel takes an actor's motion only where its depth matches the scene's; anything else keeps the camera motion. RX_VK_DLSS_ACTORS=0 turns it off, and RX_VK_DLSS_DEBUG=motion shows the actors' vectors.

Measured by running Link past the camera at a 2048x1152 output (headless, frame clock, same save), the mean error over Link against the game's own upscale drops by 40% (16.0 → 9.7, lower in every frame); over the whole frame 12.5 → 7.9. The runs differ by a few pixels in Link's position, so the numbers are indicative. A control with the camera turning and Link standing is unchanged (13.78 vs 13.81). Cost: about 0.04 ms of GPU and 0.07 ms of CPU a frame.

Still on the camera's motion: particles (Navi), water, the floor mist, foliage moved in its vertex shader and other mesh-shader geometry, Link's face and hair (drawn with another layout), rigid objects without bones, and an actor's first frame on screen.

Two test switches come with it: RX_VK_DLSS_TRACE logs one frame's draws into the scene (how the above was found), and RX_VK_SHOT_GO=<file> takes a screenshot at every present while that file exists.

Settings

DLSS is a choice in the Graphics tab's Upscaler row (Game's own, NVIDIA DLSS, NVIDIA DLAA), which replaces the separate NVIDIA DLSS row. The scaling rows read in the picture's order: Output resolution, Upscaler, Render resolution, Window scaling filter (formerly Upscaling), Sharpness. NVIDIA Reflex sits with Frame pacing.

With DLSS, Render resolution shows DLSS's modes: Auto, Quality (67%), Balanced (58%), Performance (50%) and Ultra Performance (33%). With DLAA it shows Native. DLSS now replaces the game's upscale at every one of these shares, not only at 75%: at the other shares the game runs a different upscale network, which DLSS previously left running.

DLSS and DLAA are offered only where they can run (an RTX GPU, the runtime present), and the help says why when they are not. In a build without NVIDIA's SDK, which includes every official build, the Upscaler row is not shown at all, and Render resolution is the game's own row. The Window scaling filter's help says it matters only when the window's size differs from the Output resolution; while they match, the row shows "Not needed (window = output)" and keeps the saved value. Reflex likewise needs VK_NV_low_latency2. Quality presets leave the Upscaler as chosen (with DLSS, their resolution becomes DLSS's nearest mode), and RX_VK_DLSS=0/1/2 still overrides the row. NVIDIA DLSS and DLAA look softer than the game's own upscaler, so their help says to set Sharpness to around 80%.

The menu's help is now a short line on what a row is, plus a separate line for the value chosen (dimmer, with a bar), so the details sit with the value they describe; the panel keeps its height as values change.

The five settings read only at start (GPU device, Output resolution, Handheld profile, Ultrawide, Robust buffers) show "(after restart)" only while they differ from what runs, the Graphics tab lists them at its foot, and closing the menu asks "Restart to apply …?" with [Later] (keeps the change for the next start, asked again only after another change) or [Restart now]. Restart now saves the options and the pipeline cache, closes the game the normal way (the start-up screen's close path), then starts it again with the same arguments and environment: on Linux by re-executing in the same process (a launcher or stream waiting on it keeps it), on Windows with CreateProcessW. Tested headless: Output resolution 2048x1152 → 2560x1440 across a restart, options and pipeline cache written, saves untouched. Tests: tests/test_menu_restart.py, plus additions to test_menu_hit and test_upscaler_rows.

Measurements

RTX 3090, Linux, driver 615.78, 2560x1440 output, Kokiri Forest start spot, 45 s per run after a 15 s settle, each run alone on the GPU. fps is the median of the renderer's own present counts, with p10–p90 in brackets.

Mode What runs fps Frame time
Native network the game's 4/3 upscale 50.2 (43.1–51.4) 19.9 ms
DLSS 1920x1080 → 2560x1440, preset K 45.1 (42.6–47.0) 22.2 ms
DLAA 2560x1440 → 2560x1440 43.7 (42.0–44.9) 22.9 ms

DLSS costs about 2.3 ms a frame over the game's own network, and DLAA about 3 ms. DLAA draws 1.78x the pixels but is only 0.7 ms slower than DLSS, because the frame here is mostly bound by the CPU and replay rather than by the GPU.

Reflex, in a 1280x720 window with DLSS off, using the driver's own vkGetLatencyTimingsNV reports of simulation start to present end:

Reflex fps Latency (median)
Off 47.0 21.0 ms
On 49.8 19.9 ms
On + Boost 49.9 19.9 ms

Reflex saves about 1 ms, close to run-to-run noise: at this CPU-bound frame rate the GPU queue isn't deep enough to add much delay. Boost made no difference. The Off run had a few hitches; with one run per mode, it isn't clear whether Reflex removes those.

Windows

DLSS Super Resolution, DLAA and the camera jitter now build for Windows as well; Reflex needed no change. NVIDIA's SDK is still never committed or bundled: each build downloads it for itself, only after NVIDIA's license is accepted.

scripts/ngx_sdk.py replaces scripts/linux/ngx-sdk.sh, which stays as a thin wrapper. It has one file table per platform at the pinned NVIDIA/DLSS commit 9981c635 (v310.9.16), checks every file against its SHA-256, and needs --accept-nvidia-license or a typed yes. On Windows it fetches LICENSE.txt, the headers, x64/nvsdk_ngx_s.lib and rel/nvngx_dlss.dll.

The llvm-mingw build links NVIDIA's static nvsdk_ngx_s.lib (built for MSVC's static runtime, /MT) into hookshot_vk.dll with /nodefaultlib:libcpmt and -ladvapi32. What it needs of MSVC's runtime comes from gpu/vk/vk_ngx_msvc_rt.cpp and host/mingw_msvc_shims.c, compiled only for mingw with the SDK. The DLL still imports only Windows' own DLLs and the UCRT. Without the SDK, the build is unchanged (same objects, imports, exports and section sizes).

At run time NGX is given RX_VK_DLSS_DIR (when set), <game>\ngx and %LOCALAPPDATA%\Hookshot\ngx, and uses the newest nvngx_dlss.dll it finds across them. DLSS's own paths are wide all the way (GetModuleFileNameW, _wgetenv). The game itself does not yet start from a folder whose name has characters outside the system's ANSI code page; that is a separate fix, not part of this PR. NGX's core, _nvngx.dll, is the driver's and is never put beside the game.

NGX's parameter object is read and set through the SDK's C functions (NVSDK_NGX_Parameter_GetI / GetUI / SetUI), not its C++ virtual methods: the object comes from NVIDIA's MSVC-built library, and llvm-mingw's Itanium vtable order differs, so the virtual calls only worked because the overloads they landed on had the same size.

The Hookshot Builder has an opt-in tick, "NVIDIA DLSS (downloads NVIDIA's SDK under NVIDIA's license)", off by default. Ticking it shows the license; the build then fetches the SDK into %LOCALAPPDATA%\HookshotBuilder\ngx-sdk, passes HOOKSHOT_NGX_SDK through, and make_game.py puts nvngx_dlss.dll in <game>\ngx. Updating a folder built with DLSS keeps DLSS.

A new CI workflow, vulkan-dlss, fetches the headers and .lib only, builds hookshot_vk.dll with them through llvm-mingw, and checks the link and the imports. It runs by hand, and on pushes and pull requests only when DLSS's files change (gpu/vk/vk_dlss*, vk_reflex*, vk_ngx_msvc_rt.cpp, host/mingw_msvc_shims.c, upscaler_rows.h, dlss_math.h, the DLSS shaders, nx2/oot_jitter.py, nx2/build.py, nx2/make_game.py, scripts/ngx_sdk.py and the workflow itself). It accepts NVIDIA's license for that run only and uploads nothing.

The Builder's C# is compiled by CI (the Compile Windows Builder step: 0 errors), and its builder workers test now passes Runner.Info's new ngxSdk argument.

NGX's log (RX_VK_DLSS_LOG=1) has a few lines that look like errors and are not: "failed to load NGXCore: 126" (twice) is NVIDIA's library looking for its core beside the game before it loads the driver's; "Feature … failed to load from cache" is NGX finding none of its own downloaded models. The readme says so. The two Windows warnings at init, "passed vkGetInstance/DeviceProcAddr to NGX SDK, but snippet is out of date" and the same for NVSDK_NGX_FeatureCommonInfo, come from NGX probing every feature snippet it knows at init (denoiser, inpainting, DLISR, slow-mo and so on, as its log shows on Linux), some of them older ones of the driver's. Our own DLSS snippet is 310.9.16, the SDK's. Hookshot uses the current init (NVSDK_NGX_VULKAN_Init_with_ProjectID with both procedure addresses and the feature info), and both are needed: the procedure addresses because the renderer loads Vulkan itself, and the feature info for the search paths and the logging. So the warnings are left as they are.

Never share a game folder built with NVIDIA's files. The readme credits NVIDIA DLSS; NVIDIA's files are under NVIDIA's RTX SDKs license.

Known limits of the shims: they don't check /GS cookies in the exception-handler path (the same as the existing __GSHandlerCheck), and running out of memory inside NGX ends the process with a message instead of throwing.

Windows checks (12 of 14 done on Windows 11, RTX 5080, driver 621.14; last run on 842121f)

  • dotnet build builder/HookshotBuilder.sln compiles: a full rebuild gives 0 warnings, 0 errors (.NET SDK 10.0.403, net8.0-windows); CI's Compile Windows Builder step also compiles it
  • Builder: the NVIDIA DLSS tick is off by default; ticking it shows NVIDIA's license; No unticks it; Yes keeps it, and it is still ticked after restarting the builder (it was saved only when Build was pressed; it is now saved as soon as it changes)
  • Builder build with the tick: a fresh download of the license, 23 headers, nvsdk_ngx_s.lib and nvngx_dlss.dll, all hash-checked; the license notice, fetch lines and "with NVIDIA DLSS" in the Builder's log; ngx\nvngx_dlss.dll in the game folder with the right SHA-256; no _nvngx.dll anywhere. (bin\ needs a packaged Builder to check: a Builder run from the source tree has none)
  • Builder build without the tick: no download, no ngx folder or NVIDIA files, no NGX code in hookshot_vk.dll, and the menu has no Upscaler row (NVIDIA Reflex is still there: it needs no SDK)
  • The game's update offer on a DLSS-built folder rebuilds it with DLSS. Partly: the rebuild it runs (make_game.py --update with HOOKSHOT_NGX_SDK) rebuilt a DLSS folder "with NVIDIA DLSS" in 7.5 min, put nvngx_dlss.dll back and kept the saves; the launcher's Update button itself only shows when a newer release exists
  • RTX GPU: "NGX started" every time; DLAA at 3840x2160 and 2560x1440; DLSS Quality 1707x960 → 2560x1440 and 2560x1440 → 3840x2160, preset K; switching DLSS ↔ DLAA mid-run; DLSS letting its resources go after 120 unused frames and starting again; the jitter changing frame to frame; Reflex On and On + Boost with their timing reports
  • Non-NVIDIA GPU (or no NGX): NGX fails with a log line and the game's own upscaler runs. Partly: with no nvngx_dlss.dll anywhere, the log names the folders it looked in, then "the game's upscale runs (DLSS not available)", and DLSS isn't offered; a non-NVIDIA GPU or a missing NGX core is still to try
  • A game folder path with non-ASCII letters: with the folder named Hookshot-DLSS Zélda, NGX loaded …Hookshot-DLSS Zélda\ngx/nvngx_dlss.dll and started. A name outside the ANSI code page (Japanese on an English Windows) stops the game itself before DLSS runs: that is the host's path handling, fixed in a separate PR
  • %LOCALAPPDATA%\Hookshot\ngx\nvngx_dlss.dll is used when the game folder's ngx has none; RX_VK_DLSS_DIR wins over both (tested with the same version in each place; with different versions NGX takes the newest)
  • RX_VK_DLSS_LOG=1: NGX's own lines appear in our log (253 to 557 per run: NGX's core from the driver's DriverStore, "called from module hookshot_vk.dll", our snippet and version, "Using App hint Preset K"); its lines are now written as UTF-8 like ours
  • No crash at NGX start, while resizing or changing modes, or when quitting: several play sessions and seven test runs, DLSS/DLAA switches and 1280x720 / 1920x1080 swapchains, every run ending "window closed, exiting"
  • Command line: the fetch asks for yes (no → exit 2, nothing downloaded), a typed yes is accepted, tests/test_ngx_sdk.py and tests/test_make_game_ngx.py pass, and make_game.py with HOOKSHOT_NGX_SDK builds with DLSS
  • HOOKSHOT_MSVC=1 (clang, MSVC target) with HOOKSHOT_NGX_SDK: hookshot_vk.dll builds and links (291 s, MSVC 14.51, Windows SDK 10.0.26100), imports only ADVAPI32, GDI32, KERNEL32 and USER32
  • CI vulkan-dlss passes on windows-2022: green on the PR at d378bd0 (and every step was also run locally on Windows with CI's pinned llvm-mingw: 187 files, 0 failed, imports check passed)

Note for testers: NGX starts on demand (the Insert menu's first opening, or the first upscale with DLSS), so a log from the title screen has no NGX lines yet.

How to build it

  1. Run python -I scripts/ngx_sdk.py --accept-nvidia-license <sdk folder> --game <game folder> (or the Builder's NVIDIA DLSS tick on Windows; scripts/linux/ngx-sdk.sh still works). It fetches NVIDIA's DLSS SDK headers, library and runtime from NVIDIA/DLSS at one pinned commit, checks each file's SHA-256 and copies the runtime into the game folder. NVIDIA's files are under NVIDIA's own license (LICENSE.txt is fetched beside them) and are never committed here.
  2. Build with HOOKSHOT_NGX_SDK=build-linux/ngx-sdk. Without that variable the build is the same as before: no Upscaler row, and no camera jitter sites in the lifted code.
  3. Pick NVIDIA DLSS or NVIDIA DLAA in the Graphics tab's Upscaler row (or RX_VK_DLSS=1 / 2), and Reflex in its row (RX_VK_REFLEX).

Tested on Linux with an RTX 3090, driver 615.78.08. Windows: tested on Windows 11 with an RTX 5080 (driver 621.14), see the checklist.

NVIDIA DLSS for Hookshot: Super Resolution, DLAA and Reflex, through NVIDIA's NGX library and Vulkan directly (no Streamline), on Linux and Windows. Opt-in at build time and off by default: a normal build, CI's release builds and the Builder without its NVIDIA DLSS tick are unchanged and fetch nothing. No Streamline: everything uses NVIDIA's NGX library from the DLSS SDK directly, plus Vulkan's own `VK_NV_low_latency2` for Reflex, so it works on native Linux as well as Windows. **NVIDIA's files.** Nothing of NVIDIA's is committed, bundled or uploaded. Only someone who opts in fetches them, for their own build: `scripts/ngx_sdk.py` downloads them from NVIDIA's own repository (NVIDIA/DLSS on GitHub) at one pinned commit, checks every file against a SHA-256 in the script, and refuses to download until NVIDIA's license is accepted (`--accept-nvidia-license`, or the Builder's tick, which shows the license first). The only automated fetch is the `vulkan-dlss` CI workflow, which downloads the headers and the .lib to check that the Windows link still works: it runs by hand (workflow_dispatch), and on a push or pull request only when a file DLSS's build depends on changes (`paths:` filters in its own workflow file, so the other jobs are untouched). It accepts NVIDIA's license for that CI run only, and uploads and caches nothing. ## What's in it | Commit | What | |---|---| | DLSS Super Resolution | NGX in place of OoT's gameplay upscale network | | Camera jitter | sub-pixel jitter of the scene camera while DLSS draws | | DLAA | DLSS at the render size, as anti-aliasing only | | Reflex | NVIDIA Reflex latency mode and markers | | Output resolutions (#849) | DLSS follows the 2048x1152 / 2560x1440 / 3840x2160 outputs and the new Resolution row, down to a third of the output (Ultra Performance); a render size equal to the output runs as DLAA | | (merges of main, a test fix, a Reflex setup-order fix) | | ### DLSS Super Resolution - **Upscale:** NGX starts with the DLSS runtime (310.9.16) placed in the game folder's `ngx/`. DLSS then replaces OoT's gameplay upscale network (fd029a31) every frame, preset K. Input and output sizes follow the game's: with #849 the output is 2048x1152, 2560x1440 or 3840x2160 and the Resolution row scales the 3D against it; the feature is made again when the output size changes or the render size leaves its mode's range. Colours and brightness match the native path. - **Motion vectors:** reprojected from depth with the scene camera, read from vertex ubo 0 (Eden's cbuf 3). The buffer's "previous matrix" slot at 0x180 is all zeros in this game version, so the camera from the last frame DLSS drew is used instead. `RX_VK_DLSS_DEBUG=motion` shows them. - **Fallback:** any unexpected input (format, size, missing camera) falls back to the native network and logs the reason. ### Camera jitter `nx2/oot_jitter.py` patches the function that fills each view's uniform buffers (three loads of the view's projection) to add a Halton sub-pixel offset to P[0][2]/P[1][2] in registers, only while DLSS draws. Nothing in the game's memory changes: the GPU gets the jittered projection, and the CPU (shadow cascade fitting, culling) keeps the real camera. The host asks the renderer for the scene size and the sequence's length through `rx_option` (`HOS_OPT_DLSS_JITTER_W`, `_H`, `_PHASES`). The renderer reads the offset back from P[0][2]/P[1][2], passes it to DLSS and removes it from the motion vectors. Verified in game: the offsets go through the sequence as expected, with no faults. The sequence is 8 × (output / render)² frames long, 8 (DLAA) to 64, as NVIDIA's guide recommends. The sites are only lifted into a build with NVIDIA's SDK (make_game.py sets `OOT_DLSS_JITTER=1` for the lift, part of its cache key). `dlss_jitter_clip` (in `dlss_math.h`, tested) picks the projections that get it: perspective, at 16:9 or the window's ratio; orthographic and 1:1 projections (the shadow maps, the per-character projector) never do. Shadows (reported by a tester): the first version put the jitter into sead's projection itself, so the shadow cascades, fitted to the view frustum, followed it and their origin moved by about 0.17 shadow texels a frame with a still camera at DLAA 3840x2160 (up to 0.4), against 0.003 with the jitter off. With the jitter only in the uniform buffers, it is back to the jitter-off level; picture detail is unchanged and fps is the same (38.5–39.4 at DLAA 3840x2160, jitter on or off). ### DLAA DLAA is the **NVIDIA DLAA** choice of the Upscaler row (`RX_VK_DLSS=2`; see Settings). DLAA runs DLSS at a scale of 1 on the scene as drawn. At full scale the game doesn't run its 4/3 upscale but the same network as a same-size pass (9dc98871eb7fc101); DLSS replaces that pass too, which also covers the Resolution row's top step with DLSS on. The 3D is held at full scale whatever the Resolution row says, and `dlss_out.comp` scales the result to the game's upscale output. ### Reflex A new **NVIDIA Reflex** row: **Off / On / On + Boost** (`RX_VK_REFLEX=0/1/2`). - Reflex is now zero-impact when Off: no latency mode on the swapchain, no markers, no sleep, no present ids and nothing logged. Turning the row on or off makes the swapchain again at the next present. - While it is on, the swapchain is made with latency mode on, and each frame the driver gets its markers, keyed by the present id: simulation start and input after `vkLatencySleepNV`, simulation end and render-submit start at the game's present, render-submit end before the present's submit, and present start/end around `vkQueuePresentKHR`. - The device's `VK_NV_low_latency2` and `VK_KHR_present_id` are still enabled whenever the device has them, so the row works without a restart. Enabled alone they do nothing: nothing of them is used until the row is on. `RX_VK_REFLEX=-` leaves them off. - `RX_VK_REFLEX_LOG=1` (opt-in) prints the driver's own timings (`vkGetLatencyTimingsNV`) every 300 frames while Reflex is on. ## Status - [x] DLSS SR runs in play; colours and brightness match native - [x] Motion vectors from depth + camera - [x] Camera jitter runs in game - [x] DLAA and Reflex: committed, build, and the full test suite passes - [x] Up to date with main, including #849's output resolutions - [x] In game at all three outputs (no faults; VRAM fine at 4K): 2048x1152 from 1536x864, 2560x1440 from 1920x1080, 3840x2160 from 2880x1620 - [x] DLAA in game: at full scale the game runs its upscale network as a same-size pass (9dc98871), which DLSS now replaces too; NGX picks DLAA mode (2560x1440 → 2560x1440) - [ ] Quality vs native at a 2560x1440 output, without jitter so far: DLSS is softer and has no halos, while the native network sharpens hard (more texture detail, halos on edges). DLSS shimmers less on a still camera (mean frame-to-frame difference 2.7 vs 3.4), brightness is the same, and no ghosting was seen on Link or Saria while panning. Still to capture: with jitter, and DLAA. The softness may want the existing Sharpness option or one of its own. - [x] fps: native vs DLSS vs DLAA, measured (see Measurements) - [x] Reflex On / On + Boost vs Off latency, measured (see Measurements) - [x] Skinned characters (Link, the other characters and animals) have their own motion vectors and no longer ghost (see Moving objects); particles, water, mist, vertex-animated foliage and Link's face and hair still use the camera's motion - [x] Windows: built and tested on Windows 11 with an RTX 5080 (see the Windows checks) - [x] A packaged way to get the runtime: the Builder's opt-in NVIDIA DLSS tick fetches it after the license is accepted (Windows); `scripts/ngx_sdk.py` elsewhere - DLSS Frame Generation is not in this PR. Newer NGX releases offer it on Linux too, so it can follow as its own change (a newer SDK pin, the feature's own inputs such as motion vectors and depth, and pacing against Hookshot's own frame generation). ## Since the review - The barriers around NGX's evaluation are recorded again: `vk_dlss.cpp` now includes `vk_work.h` and counts NGX's own commands (`note_work()` after the feature's creation and the evaluation), so `full_barrier`'s dedupe no longer leaves them out. - CI's builder workers test passes `Runner.Info`'s new `ngxSdk` argument. - Reflex is now zero-impact when Off (see Reflex). - NGX's data folder is made with the `error_code` overload: a folder that cannot be made no longer ends the game. Windows paths are wide all the way. - NGX's parameters go through the SDK's C functions, not the C++ virtual methods (an ABI mismatch under llvm-mingw). - The jitter goes only into the views' uniform buffers (not the game's cameras, so the shadow cascades stay steady), is only lifted into builds with the SDK, and its sequence is as long as DLSS's scale wants. - Another network of the upscale's shape is replaced only when its input is the gameplay networks' B10G11R11, not only its output. - DLSS's feature, images and deferred frees are let go after 120 frames unused, so turning it off gives its video memory back. - `descriptors_disturbed()` on every way out once commands are recorded; `failed` and `last_frame` are atomics; `make_pipelines` no longer leaks; a 60 s timeout on each SDK download. - The investigation switches (`RX_VK_DLSS_JITTER_SIGN`, `RX_VK_DLSS_SQUARE`) and the camera dump are gone, stale comments fixed, the jitter's options named (`HOS_OPT_DLSS_JITTER_*`). - The camera math (reprojection, jitter, Halton) is in `gpu/vk/dlss_math.h` and tested on the CPU (`tests/test_dlss_math.py`); the menu's rules are tested by behaviour in `test_upscaler_rows.cpp`. - The `vulkan-dlss` CI job is in its own workflow: manual, or on DLSS's files only. `ngx-sdk/` is git-ignored. - `host/rx_backend.c` now includes `gpu/vk/dlss_math.h`, so the host objects' cache key hashes it and the Linux AppImage carries it. - Merged with main again (016d582, with the start-up screen #860). Conflicts in `vk_main.cpp`, `vk_replay.cpp`, `nx2/build.py` and `test_shader_stamp_cache.py`, all resolved keeping both sides. - Merged with main before that (#939, #946 and the rest). One conflict, in `vk_menu.cpp`: #946 moved the Insert menu's value changes into `set_row` (keys, clicks, the wheel and slider drags all go through it), and the Upscaler's rules (rows that cannot change here stay as they are, the Upscaler and Render resolution step through `upscaler_rows.h`, another Upscaler snaps the Render resolution) moved into `set_row` with it, so the mouse follows them too. Checked again on Linux after these changes (RTX 3090): builds with and without the SDK (without it: no NGX in `hookshot_vk.so`, no `ngx` folder, no jitter sites in the lifted code), the Windows `hookshot_vk.dll` cross-built with the SDK through llvm-mingw (imports only Windows' own DLLs and the UCRT), `tests/run_all.py` green, actionlint clean. In game: DLSS draws the upscale correctly, the jitter steps through its sequence, Reflex On runs with its markers and timings, and with Reflex Off the log has no Reflex line at all. ## Moving objects OoT has no velocity buffer: its scene pass writes a single B10G11R11 colour target plus depth, and its upscale network samples only the colour, so DLSS had only the camera's motion and anything that moves by itself ghosted. Skinned draws (Link, the other characters and animals) are ordinary indexed vertex draws with a recognisable layout: position, bone weights and bone indices, with the bone palette in a uniform buffer. `vk_draw.cpp` picks those that draw into the scene, and at the upscale each is drawn again into the motion image, each vertex skinned with this frame's bones and the previous frame's. The vertex and index data are read from the game's own buffers, and the poses are copied on the GPU into two buffers that take turns, so nothing is read back to the CPU. A pixel takes an actor's motion only where its depth matches the scene's; anything else keeps the camera motion. `RX_VK_DLSS_ACTORS=0` turns it off, and `RX_VK_DLSS_DEBUG=motion` shows the actors' vectors. Measured by running Link past the camera at a 2048x1152 output (headless, frame clock, same save), the mean error over Link against the game's own upscale drops by 40% (16.0 → 9.7, lower in every frame); over the whole frame 12.5 → 7.9. The runs differ by a few pixels in Link's position, so the numbers are indicative. A control with the camera turning and Link standing is unchanged (13.78 vs 13.81). Cost: about 0.04 ms of GPU and 0.07 ms of CPU a frame. Still on the camera's motion: particles (Navi), water, the floor mist, foliage moved in its vertex shader and other mesh-shader geometry, Link's face and hair (drawn with another layout), rigid objects without bones, and an actor's first frame on screen. Two test switches come with it: `RX_VK_DLSS_TRACE` logs one frame's draws into the scene (how the above was found), and `RX_VK_SHOT_GO=<file>` takes a screenshot at every present while that file exists. ## Settings DLSS is a choice in the Graphics tab's **Upscaler** row (Game's own, NVIDIA DLSS, NVIDIA DLAA), which replaces the separate NVIDIA DLSS row. The scaling rows read in the picture's order: Output resolution, Upscaler, Render resolution, Window scaling filter (formerly Upscaling), Sharpness. NVIDIA Reflex sits with Frame pacing. With DLSS, Render resolution shows DLSS's modes: Auto, Quality (67%), Balanced (58%), Performance (50%) and Ultra Performance (33%). With DLAA it shows Native. DLSS now replaces the game's upscale at every one of these shares, not only at 75%: at the other shares the game runs a different upscale network, which DLSS previously left running. DLSS and DLAA are offered only where they can run (an RTX GPU, the runtime present), and the help says why when they are not. In a build without NVIDIA's SDK, which includes every official build, the Upscaler row is not shown at all, and Render resolution is the game's own row. The Window scaling filter's help says it matters only when the window's size differs from the Output resolution; while they match, the row shows "Not needed (window = output)" and keeps the saved value. Reflex likewise needs VK_NV_low_latency2. Quality presets leave the Upscaler as chosen (with DLSS, their resolution becomes DLSS's nearest mode), and `RX_VK_DLSS=0/1/2` still overrides the row. NVIDIA DLSS and DLAA look softer than the game's own upscaler, so their help says to set Sharpness to around 80%. The menu's help is now a short line on what a row is, plus a separate line for the value chosen (dimmer, with a bar), so the details sit with the value they describe; the panel keeps its height as values change. The five settings read only at start (GPU device, Output resolution, Handheld profile, Ultrawide, Robust buffers) show "(after restart)" only while they differ from what runs, the Graphics tab lists them at its foot, and closing the menu asks "Restart to apply …?" with [Later] (keeps the change for the next start, asked again only after another change) or [Restart now]. Restart now saves the options and the pipeline cache, closes the game the normal way (the start-up screen's close path), then starts it again with the same arguments and environment: on Linux by re-executing in the same process (a launcher or stream waiting on it keeps it), on Windows with `CreateProcessW`. Tested headless: Output resolution 2048x1152 → 2560x1440 across a restart, options and pipeline cache written, saves untouched. Tests: `tests/test_menu_restart.py`, plus additions to `test_menu_hit` and `test_upscaler_rows`. ## Measurements RTX 3090, Linux, driver 615.78, 2560x1440 output, Kokiri Forest start spot, 45 s per run after a 15 s settle, each run alone on the GPU. fps is the median of the renderer's own present counts, with p10–p90 in brackets. | Mode | What runs | fps | Frame time | |---|---|---|---| | Native network | the game's 4/3 upscale | 50.2 (43.1–51.4) | 19.9 ms | | DLSS | 1920x1080 → 2560x1440, preset K | 45.1 (42.6–47.0) | 22.2 ms | | DLAA | 2560x1440 → 2560x1440 | 43.7 (42.0–44.9) | 22.9 ms | DLSS costs about 2.3 ms a frame over the game's own network, and DLAA about 3 ms. DLAA draws 1.78x the pixels but is only 0.7 ms slower than DLSS, because the frame here is mostly bound by the CPU and replay rather than by the GPU. Reflex, in a 1280x720 window with DLSS off, using the driver's own `vkGetLatencyTimingsNV` reports of simulation start to present end: | Reflex | fps | Latency (median) | |---|---|---| | Off | 47.0 | 21.0 ms | | On | 49.8 | 19.9 ms | | On + Boost | 49.9 | 19.9 ms | Reflex saves about 1 ms, close to run-to-run noise: at this CPU-bound frame rate the GPU queue isn't deep enough to add much delay. Boost made no difference. The Off run had a few hitches; with one run per mode, it isn't clear whether Reflex removes those. ## Windows DLSS Super Resolution, DLAA and the camera jitter now build for Windows as well; Reflex needed no change. NVIDIA's SDK is still never committed or bundled: each build downloads it for itself, only after NVIDIA's license is accepted. `scripts/ngx_sdk.py` replaces `scripts/linux/ngx-sdk.sh`, which stays as a thin wrapper. It has one file table per platform at the pinned NVIDIA/DLSS commit 9981c635 (v310.9.16), checks every file against its SHA-256, and needs `--accept-nvidia-license` or a typed yes. On Windows it fetches LICENSE.txt, the headers, `x64/nvsdk_ngx_s.lib` and `rel/nvngx_dlss.dll`. The llvm-mingw build links NVIDIA's static `nvsdk_ngx_s.lib` (built for MSVC's static runtime, /MT) into `hookshot_vk.dll` with `/nodefaultlib:libcpmt` and `-ladvapi32`. What it needs of MSVC's runtime comes from `gpu/vk/vk_ngx_msvc_rt.cpp` and `host/mingw_msvc_shims.c`, compiled only for mingw with the SDK. The DLL still imports only Windows' own DLLs and the UCRT. Without the SDK, the build is unchanged (same objects, imports, exports and section sizes). At run time NGX is given `RX_VK_DLSS_DIR` (when set), `<game>\ngx` and `%LOCALAPPDATA%\Hookshot\ngx`, and uses the newest `nvngx_dlss.dll` it finds across them. DLSS's own paths are wide all the way (`GetModuleFileNameW`, `_wgetenv`). The game itself does not yet start from a folder whose name has characters outside the system's ANSI code page; that is a separate fix, not part of this PR. NGX's core, `_nvngx.dll`, is the driver's and is never put beside the game. NGX's parameter object is read and set through the SDK's C functions (`NVSDK_NGX_Parameter_GetI` / `GetUI` / `SetUI`), not its C++ virtual methods: the object comes from NVIDIA's MSVC-built library, and llvm-mingw's Itanium vtable order differs, so the virtual calls only worked because the overloads they landed on had the same size. The Hookshot Builder has an opt-in tick, "NVIDIA DLSS (downloads NVIDIA's SDK under NVIDIA's license)", off by default. Ticking it shows the license; the build then fetches the SDK into `%LOCALAPPDATA%\HookshotBuilder\ngx-sdk`, passes `HOOKSHOT_NGX_SDK` through, and `make_game.py` puts `nvngx_dlss.dll` in `<game>\ngx`. Updating a folder built with DLSS keeps DLSS. A new CI workflow, `vulkan-dlss`, fetches the headers and .lib only, builds `hookshot_vk.dll` with them through llvm-mingw, and checks the link and the imports. It runs by hand, and on pushes and pull requests only when DLSS's files change (`gpu/vk/vk_dlss*`, `vk_reflex*`, `vk_ngx_msvc_rt.cpp`, `host/mingw_msvc_shims.c`, `upscaler_rows.h`, `dlss_math.h`, the DLSS shaders, `nx2/oot_jitter.py`, `nx2/build.py`, `nx2/make_game.py`, `scripts/ngx_sdk.py` and the workflow itself). It accepts NVIDIA's license for that run only and uploads nothing. The Builder's C# is compiled by CI (the Compile Windows Builder step: 0 errors), and its builder workers test now passes `Runner.Info`'s new `ngxSdk` argument. NGX's log (`RX_VK_DLSS_LOG=1`) has a few lines that look like errors and are not: "failed to load NGXCore: 126" (twice) is NVIDIA's library looking for its core beside the game before it loads the driver's; "Feature … failed to load from cache" is NGX finding none of its own downloaded models. The readme says so. The two Windows warnings at init, "passed vkGetInstance/DeviceProcAddr to NGX SDK, but snippet is out of date" and the same for `NVSDK_NGX_FeatureCommonInfo`, come from NGX probing every feature snippet it knows at init (denoiser, inpainting, DLISR, slow-mo and so on, as its log shows on Linux), some of them older ones of the driver's. Our own DLSS snippet is 310.9.16, the SDK's. Hookshot uses the current init (`NVSDK_NGX_VULKAN_Init_with_ProjectID` with both procedure addresses and the feature info), and both are needed: the procedure addresses because the renderer loads Vulkan itself, and the feature info for the search paths and the logging. So the warnings are left as they are. Never share a game folder built with NVIDIA's files. The readme credits NVIDIA DLSS; NVIDIA's files are under NVIDIA's RTX SDKs license. Known limits of the shims: they don't check /GS cookies in the exception-handler path (the same as the existing `__GSHandlerCheck`), and running out of memory inside NGX ends the process with a message instead of throwing. ### Windows checks (12 of 14 done on Windows 11, RTX 5080, driver 621.14; last run on 842121f) - [x] `dotnet build builder/HookshotBuilder.sln` compiles: a full rebuild gives 0 warnings, 0 errors (.NET SDK 10.0.403, net8.0-windows); CI's Compile Windows Builder step also compiles it - [x] Builder: the NVIDIA DLSS tick is off by default; ticking it shows NVIDIA's license; No unticks it; Yes keeps it, and it is still ticked after restarting the builder (it was saved only when Build was pressed; it is now saved as soon as it changes) - [x] Builder build with the tick: a fresh download of the license, 23 headers, `nvsdk_ngx_s.lib` and `nvngx_dlss.dll`, all hash-checked; the license notice, fetch lines and "with NVIDIA DLSS" in the Builder's log; `ngx\nvngx_dlss.dll` in the game folder with the right SHA-256; no `_nvngx.dll` anywhere. (`bin\` needs a packaged Builder to check: a Builder run from the source tree has none) - [x] Builder build without the tick: no download, no `ngx` folder or NVIDIA files, no NGX code in `hookshot_vk.dll`, and the menu has no Upscaler row (NVIDIA Reflex is still there: it needs no SDK) - [ ] The game's update offer on a DLSS-built folder rebuilds it with DLSS. Partly: the rebuild it runs (`make_game.py --update` with `HOOKSHOT_NGX_SDK`) rebuilt a DLSS folder "with NVIDIA DLSS" in 7.5 min, put `nvngx_dlss.dll` back and kept the saves; the launcher's Update button itself only shows when a newer release exists - [x] RTX GPU: "NGX started" every time; DLAA at 3840x2160 and 2560x1440; DLSS Quality 1707x960 → 2560x1440 and 2560x1440 → 3840x2160, preset K; switching DLSS ↔ DLAA mid-run; DLSS letting its resources go after 120 unused frames and starting again; the jitter changing frame to frame; Reflex On and On + Boost with their timing reports - [ ] Non-NVIDIA GPU (or no NGX): NGX fails with a log line and the game's own upscaler runs. Partly: with no `nvngx_dlss.dll` anywhere, the log names the folders it looked in, then "the game's upscale runs (DLSS not available)", and DLSS isn't offered; a non-NVIDIA GPU or a missing NGX core is still to try - [x] A game folder path with non-ASCII letters: with the folder named `Hookshot-DLSS Zélda`, NGX loaded `…Hookshot-DLSS Zélda\ngx/nvngx_dlss.dll` and started. A name outside the ANSI code page (Japanese on an English Windows) stops the game itself before DLSS runs: that is the host's path handling, fixed in a separate PR - [x] `%LOCALAPPDATA%\Hookshot\ngx\nvngx_dlss.dll` is used when the game folder's `ngx` has none; `RX_VK_DLSS_DIR` wins over both (tested with the same version in each place; with different versions NGX takes the newest) - [x] `RX_VK_DLSS_LOG=1`: NGX's own lines appear in our log (253 to 557 per run: NGX's core from the driver's DriverStore, "called from module hookshot_vk.dll", our snippet and version, "Using App hint Preset K"); its lines are now written as UTF-8 like ours - [x] No crash at NGX start, while resizing or changing modes, or when quitting: several play sessions and seven test runs, DLSS/DLAA switches and 1280x720 / 1920x1080 swapchains, every run ending "window closed, exiting" - [x] Command line: the fetch asks for yes (no → exit 2, nothing downloaded), a typed yes is accepted, `tests/test_ngx_sdk.py` and `tests/test_make_game_ngx.py` pass, and `make_game.py` with `HOOKSHOT_NGX_SDK` builds with DLSS - [x] `HOOKSHOT_MSVC=1` (clang, MSVC target) with `HOOKSHOT_NGX_SDK`: `hookshot_vk.dll` builds and links (291 s, MSVC 14.51, Windows SDK 10.0.26100), imports only ADVAPI32, GDI32, KERNEL32 and USER32 - [x] CI `vulkan-dlss` passes on windows-2022: green on the PR at d378bd0 (and every step was also run locally on Windows with CI's pinned llvm-mingw: 187 files, 0 failed, imports check passed) Note for testers: NGX starts on demand (the Insert menu's first opening, or the first upscale with DLSS), so a log from the title screen has no NGX lines yet. ## How to build it 1. Run `python -I scripts/ngx_sdk.py --accept-nvidia-license <sdk folder> --game <game folder>` (or the Builder's NVIDIA DLSS tick on Windows; `scripts/linux/ngx-sdk.sh` still works). It fetches NVIDIA's DLSS SDK headers, library and runtime from NVIDIA/DLSS at one pinned commit, checks each file's SHA-256 and copies the runtime into the game folder. NVIDIA's files are under NVIDIA's own license (LICENSE.txt is fetched beside them) and are never committed here. 2. Build with `HOOKSHOT_NGX_SDK=build-linux/ngx-sdk`. Without that variable the build is the same as before: no Upscaler row, and no camera jitter sites in the lifted code. 3. Pick NVIDIA DLSS or NVIDIA DLAA in the Graphics tab's Upscaler row (or `RX_VK_DLSS=1` / `2`), and Reflex in its row (`RX_VK_REFLEX`). Tested on Linux with an RTX 3090, driver 615.78.08. Windows: tested on Windows 11 with an RTX 5080 (driver 621.14), see the checklist.
NotRexed commented 2026-11-09 05:30:31 +00:00 (Migrated from github.com)

Thank you to everyone on this collaboration branch! It's marked work-in-progress, overlaps #850, and its script fetches NVIDIA SDK files from raw.githubusercontent.com/NVIDIA/DLSS at build time. Closing to keep the queue clear; please reopen when it's ready for review as a focused PR.

Thank you to everyone on this collaboration branch! It's marked work-in-progress, overlaps #850, and its script fetches NVIDIA SDK files from raw.githubusercontent.com/NVIDIA/DLSS at build time. Closing to keep the queue clear; please reopen when it's ready for review as a focused PR.
hckrman101 commented 2026-11-09 06:58:35 +00:00 (Migrated from github.com)

Re-opening, it was tested on Windows, will update with results shortly

Re-opening, it was tested on Windows, will update with results shortly
hckrman101 commented 2026-11-09 07:24:08 +00:00 (Migrated from github.com)

Update:

  • Settings: DLSS is now a choice in a new Upscaler row (Game's own / NVIDIA DLSS / NVIDIA DLAA), and with DLSS the Render resolution row shows DLSS's modes (Quality 67%, Balanced 58%, Performance 50%, Ultra Performance 33%). This also fixed a real gap: at shares other than 75% the game runs a different upscale network, which DLSS used to leave running; DLSS now takes over at every share.
  • Up to date with main.
  • Windows: tested on Windows 11 with an RTX 5080 (driver 621.14). DLSS, DLAA, the jitter and Reflex (On / On + Boost) work in play; mode changes, resizing and quitting are fine; the opt-in fetch and the build work, and so does the HOOKSHOT_MSVC=1 link. 6 of the 14 Windows checks are done; the description has the details.
  • In progress, before this is ready to merge:
    • fixes from a review pass: barriers around the NGX evaluate, Reflex doing nothing at all while Off, NGX parameters through NVIDIA's C functions so the llvm-mingw build doesn't depend on MSVC's vtable layout, wide paths on Windows, freeing VRAM when DLSS is off;
    • the vulkan-dlss CI job becomes manual-only, so CI never fetches NVIDIA's files on its own;
    • per-object motion, so things that move by themselves stop trailing.

I'll post again when those are in.

Update: - **Settings:** DLSS is now a choice in a new Upscaler row (Game's own / NVIDIA DLSS / NVIDIA DLAA), and with DLSS the Render resolution row shows DLSS's modes (Quality 67%, Balanced 58%, Performance 50%, Ultra Performance 33%). This also fixed a real gap: at shares other than 75% the game runs a different upscale network, which DLSS used to leave running; DLSS now takes over at every share. - **Up to date with main.** - **Windows:** tested on Windows 11 with an RTX 5080 (driver 621.14). DLSS, DLAA, the jitter and Reflex (On / On + Boost) work in play; mode changes, resizing and quitting are fine; the opt-in fetch and the build work, and so does the `HOOKSHOT_MSVC=1` link. 6 of the 14 Windows checks are done; the description has the details. - **In progress, before this is ready to merge:** - fixes from a review pass: barriers around the NGX evaluate, Reflex doing nothing at all while Off, NGX parameters through NVIDIA's C functions so the llvm-mingw build doesn't depend on MSVC's vtable layout, wide paths on Windows, freeing VRAM when DLSS is off; - the `vulkan-dlss` CI job becomes manual-only, so CI never fetches NVIDIA's files on its own; - per-object motion, so things that move by themselves stop trailing. I'll post again when those are in.
hckrman101 commented 2026-11-09 08:46:48 +00:00 (Migrated from github.com)

Update: the review's findings are fixed, and the Windows test results are in. Main is merged again.

  • Windows (RTX 5080, Windows 11, driver 621.14): 6 of the 14 checks pass. DLSS Quality, DLAA, the jitter and Reflex all work in play, with no crashes when NGX starts, on mode switches, on resizing or on quitting. The HOOKSHOT_MSVC=1 build links too. The checklist in the description is updated.
  • From the Windows report:
    • NGX's parameters now go through the SDK's C functions. The C++ virtual calls only worked under llvm-mingw by luck of the vtable layout.
    • Windows paths are wide all the way, so non-ASCII folder names work.
    • The readme now says NGX takes the newest DLSS runtime it finds, and explains the harmless NGX log lines. The "snippet is out of date" warnings come from NGX probing its other features' snippets, so they are left as they are.
  • Review fixes:
    • The barriers around NGX's work are recorded again.
    • Reflex is zero-impact when Off.
    • The jitter gives each camera its offset back, and is only lifted into builds with the SDK.
    • DLSS frees its video memory after 120 frames off.
    • Stricter matching of the network it replaces.
    • Atomics, leaks and descriptors_disturbed on every path.
    • A download timeout, and the investigation switches are removed.
    • The camera math is in dlss_math.h with CPU tests.
  • Menu: the Upscaler row is hidden in builds without NVIDIA's SDK. The Window scaling filter row shows "Not needed (window = output)" when the window matches the output.
  • CI: vulkan-dlss is now its own workflow. It runs by hand, or on pushes and PRs that touch DLSS's files. The builder workers test is fixed for the new Runner.Info argument.
  • Merge: one conflict, in vk_menu.cpp. #946's set_row now carries the Upscaler rules, so the mouse follows them too.

Re-checked on Linux (RTX 3090):

  • builds with and without the SDK;
  • the Windows DLL cross-builds with the SDK;
  • tests/run_all.py passes;
  • in game: DLSS, the jitter, Reflex On, and Reflex Off with no Reflex activity.

Still open: per-object motion vectors (objects that move by themselves can trail). That work is in progress and will be a separate update.

Update: the review's findings are fixed, and the Windows test results are in. Main is merged again. - **Windows (RTX 5080, Windows 11, driver 621.14):** 6 of the 14 checks pass. DLSS Quality, DLAA, the jitter and Reflex all work in play, with no crashes when NGX starts, on mode switches, on resizing or on quitting. The `HOOKSHOT_MSVC=1` build links too. The checklist in the description is updated. - **From the Windows report:** - NGX's parameters now go through the SDK's C functions. The C++ virtual calls only worked under llvm-mingw by luck of the vtable layout. - Windows paths are wide all the way, so non-ASCII folder names work. - The readme now says NGX takes the newest DLSS runtime it finds, and explains the harmless NGX log lines. The "snippet is out of date" warnings come from NGX probing its other features' snippets, so they are left as they are. - **Review fixes:** - The barriers around NGX's work are recorded again. - Reflex is zero-impact when Off. - The jitter gives each camera its offset back, and is only lifted into builds with the SDK. - DLSS frees its video memory after 120 frames off. - Stricter matching of the network it replaces. - Atomics, leaks and `descriptors_disturbed` on every path. - A download timeout, and the investigation switches are removed. - The camera math is in `dlss_math.h` with CPU tests. - **Menu:** the Upscaler row is hidden in builds without NVIDIA's SDK. The Window scaling filter row shows "Not needed (window = output)" when the window matches the output. - **CI:** `vulkan-dlss` is now its own workflow. It runs by hand, or on pushes and PRs that touch DLSS's files. The builder workers test is fixed for the new `Runner.Info` argument. - **Merge:** one conflict, in `vk_menu.cpp`. #946's `set_row` now carries the Upscaler rules, so the mouse follows them too. Re-checked on Linux (RTX 3090): - builds with and without the SDK; - the Windows DLL cross-builds with the SDK; - `tests/run_all.py` passes; - in game: DLSS, the jitter, Reflex On, and Reflex Off with no Reflex activity. Still open: per-object motion vectors (objects that move by themselves can trail). That work is in progress and will be a separate update.
hckrman101 commented 2026-11-09 09:15:40 +00:00 (Migrated from github.com)

Update: moving characters no longer ghost under DLSS.

OoT has no velocity buffer (one B10G11R11 colour target, and the upscale network samples only colour), so DLSS only had the camera's motion and Link trailed when he moved. Now the skinned draws (Link, the other characters and animals) are drawn again at the upscale, each vertex skinned with this frame's and the previous frame's bones. The data is read straight from the game's buffers and the poses are copied on the GPU, so each actor's pixels get their own motion vectors. A pixel takes the actor's motion only where its depth matches the scene. Everything else keeps the camera's motion.

  • Link running past the camera: the error over Link against the game's own upscale is 40% lower (16.0 → 9.7, lower in every frame). These numbers are indicative, since the runs differ by a few pixels in Link's position. A control with only the camera turning is unchanged.
  • Cost: about 0.04 ms of GPU and 0.07 ms of CPU a frame.
  • RX_VK_DLSS_ACTORS=0 turns it off. RX_VK_DLSS_DEBUG=motion shows the vectors.
  • Still on camera motion: particles, water, mist, vertex-animated foliage, Link's face and hair, rigid objects without bones, and an actor's first frame on screen.

Details are in the description's new "Moving objects" section. Tests pass (120 passed, 7 skipped).

Update: moving characters no longer ghost under DLSS. OoT has no velocity buffer (one B10G11R11 colour target, and the upscale network samples only colour), so DLSS only had the camera's motion and Link trailed when he moved. Now the skinned draws (Link, the other characters and animals) are drawn again at the upscale, each vertex skinned with this frame's and the previous frame's bones. The data is read straight from the game's buffers and the poses are copied on the GPU, so each actor's pixels get their own motion vectors. A pixel takes the actor's motion only where its depth matches the scene. Everything else keeps the camera's motion. - Link running past the camera: the error over Link against the game's own upscale is 40% lower (16.0 → 9.7, lower in every frame). These numbers are indicative, since the runs differ by a few pixels in Link's position. A control with only the camera turning is unchanged. - Cost: about 0.04 ms of GPU and 0.07 ms of CPU a frame. - `RX_VK_DLSS_ACTORS=0` turns it off. `RX_VK_DLSS_DEBUG=motion` shows the vectors. - Still on camera motion: particles, water, mist, vertex-animated foliage, Link's face and hair, rigid objects without bones, and an actor's first frame on screen. Details are in the description's new "Moving objects" section. Tests pass (120 passed, 7 skipped).
wartechnology commented 2026-11-09 12:49:37 +00:00 (Migrated from github.com)

Notes from a parallel DLSS test on v2.0.7 (Streamline 2.14.16, RTX 5080, driver 621.14), where I ported this PR's frustum-offset jitter (nx2/oot_jitter.py + hos_dlss_jitter_cam) as is: stable and sharp in DLAA. A viewport-offset jitter I tried first shook badly, since the fog / lighting / AO passes rebuild positions with the unjittered camera.

Two things I saw with the jitter on, in case they show here too:

  • Shadows flicker a little (DLAA at 3840x2160). Halving the offset reduces it but softens textures. Possibly the shadow cascades are fitted to the jittered frustum.
  • DLAA at native 4K costs about 10–15 fps more with the jitter on than off (the camera "moves" every frame); Performance about 2 fps.

Unrelated but very visible under DLSS: the blocky light halos and the checkerboard smoke go away when the few NEAREST samplers are forced linear, see #1117.

Notes from a parallel DLSS test on v2.0.7 (Streamline 2.14.16, RTX 5080, driver 621.14), where I ported this PR's frustum-offset jitter (`nx2/oot_jitter.py` + `hos_dlss_jitter_cam`) as is: stable and sharp in DLAA. A viewport-offset jitter I tried first shook badly, since the fog / lighting / AO passes rebuild positions with the unjittered camera. Two things I saw with the jitter on, in case they show here too: - **Shadows flicker a little** (DLAA at 3840x2160). Halving the offset reduces it but softens textures. Possibly the shadow cascades are fitted to the jittered frustum. - **DLAA at native 4K costs about 10–15 fps more with the jitter on than off** (the camera "moves" every frame); Performance about 2 fps. <!-- screenshots / clip: DLAA, jitter off vs on --> Unrelated but very visible under DLSS: the blocky light halos and the checkerboard smoke go away when the few NEAREST samplers are forced linear, see #1117.
hckrman101 commented 2026-11-09 15:49:55 +00:00 (Migrated from github.com)

Thanks for the detailed notes, and good to hear the jitter holds up in your build.

Shadows: your guess was right. Our jitter went into the game's camera projection itself, so the CPU side saw it too, and the shadow cascades (fitted to the view frustum) followed it. With a still camera at DLAA 3840x2160, the shadow map's origin moved by about 0.17 shadow texels every frame (up to 0.4), against 0.003 with the jitter off.

The jitter now goes only into the GPU's copy of the projection: it's added in registers where the game fills each view's uniform buffers, and nothing in the game's memory changes. DLSS gets the same jitter and picture detail is unchanged. The cascades, culling and everything else on the CPU see the real camera, and the cascade movement is back to the jitter-off level. This is pushed now (4ee204a).

In my tests (RTX 3090, Kokiri Forest's dappled light) the on-screen difference was small. If you can, please check whether it's visibly calmer on your side, ideally somewhere with crisp sunlit shadows.

fps: I couldn't reproduce the jitter cost. At DLAA 3840x2160 I get 38.5–39.4 fps with the jitter on or off, before and after this change, so it may be specific to the Streamline path or the GPU. Here the shadow maps are re-rendered every frame either way.

#1117: thanks for the pointer, I'll try it together with DLSS.

Thanks for the detailed notes, and good to hear the jitter holds up in your build. **Shadows:** your guess was right. Our jitter went into the game's camera projection itself, so the CPU side saw it too, and the shadow cascades (fitted to the view frustum) followed it. With a still camera at DLAA 3840x2160, the shadow map's origin moved by about 0.17 shadow texels every frame (up to 0.4), against 0.003 with the jitter off. The jitter now goes only into the GPU's copy of the projection: it's added in registers where the game fills each view's uniform buffers, and nothing in the game's memory changes. DLSS gets the same jitter and picture detail is unchanged. The cascades, culling and everything else on the CPU see the real camera, and the cascade movement is back to the jitter-off level. This is pushed now (4ee204a). In my tests (RTX 3090, Kokiri Forest's dappled light) the on-screen difference was small. If you can, please check whether it's visibly calmer on your side, ideally somewhere with crisp sunlit shadows. **fps:** I couldn't reproduce the jitter cost. At DLAA 3840x2160 I get 38.5–39.4 fps with the jitter on or off, before and after this change, so it may be specific to the Streamline path or the GPU. Here the shadow maps are re-rendered every frame either way. **#1117:** thanks for the pointer, I'll try it together with DLSS.
hckrman101 commented 2026-11-09 16:07:06 +00:00 (Migrated from github.com)

Update: merged main again (016d582, with the start-up screen), plus three changes.

  • Shadows: the jitter now goes only into the GPU's copy of the projection, so the shadow cascades stay steady (details in my reply above). Pushed as 4ee204a.
  • Restart offer: the five settings that are read only at start (GPU device, Output resolution, Handheld profile, Ultrawide, Robust buffers) show "(after restart)" only while they differ from what runs. The Graphics tab lists them at its foot. Closing the menu then asks to restart:
    • [Later] keeps the change for the next start.
    • [Restart now] saves, closes the game the normal way and starts it again with the same arguments and environment. On Linux it stays in the same process, so a launcher or stream waiting on it keeps it.
  • Help text: each row has a short description, plus a separate line for the value chosen. NVIDIA DLSS and DLAA now say to set Sharpness to around 80%, since both look softer than the game's own upscaler.

Checked on Linux:

  • builds with and without the SDK;
  • tests pass (146);
  • headless restart from 2048x1152 to 2560x1440, with the options and pipeline cache written.

The menu agent's earlier run also cross-built hookshot_vk.dll for Windows. The full Windows exe could not be linked here (no Windows FFmpeg headers on this machine).

The description now says that DLSS's paths are wide but the game itself doesn't yet start from a folder outside the ANSI code page. That will be its own PR.

Update: merged main again (016d582, with the start-up screen), plus three changes. - **Shadows:** the jitter now goes only into the GPU's copy of the projection, so the shadow cascades stay steady (details in my reply above). Pushed as 4ee204a. - **Restart offer:** the five settings that are read only at start (GPU device, Output resolution, Handheld profile, Ultrawide, Robust buffers) show "(after restart)" only while they differ from what runs. The Graphics tab lists them at its foot. Closing the menu then asks to restart: - [Later] keeps the change for the next start. - [Restart now] saves, closes the game the normal way and starts it again with the same arguments and environment. On Linux it stays in the same process, so a launcher or stream waiting on it keeps it. - **Help text:** each row has a short description, plus a separate line for the value chosen. NVIDIA DLSS and DLAA now say to set Sharpness to around 80%, since both look softer than the game's own upscaler. Checked on Linux: - builds with and without the SDK; - tests pass (146); - headless restart from 2048x1152 to 2560x1440, with the options and pipeline cache written. The menu agent's earlier run also cross-built `hookshot_vk.dll` for Windows. The full Windows exe could not be linked here (no Windows FFmpeg headers on this machine). The description now says that DLSS's paths are wide but the game itself doesn't yet start from a folder outside the ANSI code page. That will be its own PR.
hckrman101 commented 2026-11-09 16:31:14 +00:00 (Migrated from github.com)

Thanks for the thorough Windows run on 842121f: 11 of 14 now checked. The description's checklist is updated with your results.

Fixed from your findings (pushed as d378bd0):

  • Builder tick: the NVIDIA DLSS tick is saved as soon as it changes, so ticking it, accepting the license and closing the Builder keeps it.
  • Log encoding: NGX's ngx: lines are now written as UTF-8 like the rest of the log. The fs: lines are the host's, and they go with the separate path fix.
  • On-demand NGX: the readme now says NGX starts on demand (the Insert menu's first opening, or the first upscale with DLSS), so a log from the title screen has no NGX lines yet.
  • Checklist wording: the item for builds without the tick now says the Upscaler row is hidden.

Folders outside the ANSI code page (the game reading its own path as ??? and exiting with code 2) are a host issue, not DLSS. That fix is coming as its own PR, and it also covers the command line and the fs: log lines.

CI: this push also merges the latest main (the v2.0.9 hotfix). Main had changed vk_draw.cpp's per-thread objects into functions, which broke three checks on the PR, vulkan-dlss among them. It builds again on Linux, and the Windows DLL cross-builds with the SDK; CI should confirm vulkan-dlss on windows-2022 now.

Also in the description: newer NGX releases offer DLSS Frame Generation on Linux too, so it's noted as a possible follow-up rather than Windows-only.

Thanks for the thorough Windows run on 842121f: 11 of 14 now checked. The description's checklist is updated with your results. Fixed from your findings (pushed as d378bd0): - **Builder tick:** the NVIDIA DLSS tick is saved as soon as it changes, so ticking it, accepting the license and closing the Builder keeps it. - **Log encoding:** NGX's `ngx:` lines are now written as UTF-8 like the rest of the log. The `fs:` lines are the host's, and they go with the separate path fix. - **On-demand NGX:** the readme now says NGX starts on demand (the Insert menu's first opening, or the first upscale with DLSS), so a log from the title screen has no NGX lines yet. - **Checklist wording:** the item for builds without the tick now says the Upscaler row is hidden. **Folders outside the ANSI code page** (the game reading its own path as `???` and exiting with code 2) are a host issue, not DLSS. That fix is coming as its own PR, and it also covers the command line and the `fs:` log lines. **CI:** this push also merges the latest main (the v2.0.9 hotfix). Main had changed `vk_draw.cpp`'s per-thread objects into functions, which broke three checks on the PR, `vulkan-dlss` among them. It builds again on Linux, and the Windows DLL cross-builds with the SDK; CI should confirm `vulkan-dlss` on windows-2022 now. Also in the description: newer NGX releases offer DLSS Frame Generation on Linux too, so it's noted as a possible follow-up rather than Windows-only.
hckrman101 commented 2026-11-09 17:12:54 +00:00 (Migrated from github.com)

Thanks for the retest on d378bd0. Pushed as de67a56:

  • Restart now losing the log (Windows): fixed. The new process started while the old one still had hookshot_log.txt open, so moving it to .1 failed and the new run overwrote it. The old process now passes its process id (HOS_RESTART_OF), and the new one waits for it to end (up to 10 s) before it moves the logs and opens its own. A restart should now leave the run before it in hookshot_log.1.txt, with no gaps. This was compiled for Windows but not run there yet, so a quick check would help: restart twice, then look at .1 and .2.
  • The four Z�lda lines (fs: mounts, and the program and pipeline cache paths) come from the host's ANSI path calls, outside DLSS. #1191 fixes them for the whole program, by making UTF-8 the game's code page on Windows 10 1903+. It also lets the game start from folders outside the ANSI code page (the ??? case from your first run).
  • Shadows in sharp sunlight: still to confirm by eye, if you get the chance. A still camera somewhere with crisp sun shadows (Hyrule Field, or Kokiri Forest's open patches), DLAA at 3840x2160, jitter on. Before the fix they shimmered slightly; now they should be as calm as with the jitter off (RX_VK_DLSS_JITTER=0).

Also merged main again (rumble strength #1169: both kept in the option table).

Thanks for the retest on d378bd0. Pushed as de67a56: - **Restart now losing the log (Windows):** fixed. The new process started while the old one still had `hookshot_log.txt` open, so moving it to `.1` failed and the new run overwrote it. The old process now passes its process id (`HOS_RESTART_OF`), and the new one waits for it to end (up to 10 s) before it moves the logs and opens its own. A restart should now leave the run before it in `hookshot_log.1.txt`, with no gaps. This was compiled for Windows but not run there yet, so a quick check would help: restart twice, then look at `.1` and `.2`. - **The four `Z�lda` lines** (`fs:` mounts, and the program and pipeline cache paths) come from the host's ANSI path calls, outside DLSS. #1191 fixes them for the whole program, by making UTF-8 the game's code page on Windows 10 1903+. It also lets the game start from folders outside the ANSI code page (the `???` case from your first run). - **Shadows in sharp sunlight:** still to confirm by eye, if you get the chance. A still camera somewhere with crisp sun shadows (Hyrule Field, or Kokiri Forest's open patches), DLAA at 3840x2160, jitter on. Before the fix they shimmered slightly; now they should be as calm as with the jitter off (`RX_VK_DLSS_JITTER=0`). Also merged main again (rumble strength #1169: both kept in the option table).
itaskaev2 commented 2026-11-09 18:31:24 +00:00 (Migrated from github.com)

Test report from an RTX 4080 SUPER (Ada) at a 3840x2160 output, which covers the "with jitter, and DLAA" part of the quality item.

Setup. RTX 4080 SUPER, driver 616.92, Windows 11, Ryzen 7 5800X3D, 64 GB. Built from de67a56 with llvm-mingw 20261104 and scripts/ngx_sdk.py (v310.9.16), with the 2.0.8 builder's hot list and PGO profile in work/. Kokiri Forest from a save at Saria's spot. An HOS_INPUT_SCRIPT with HOS_INPUT_FRAME_CLOCK=1 loads it and swings the camera sideways for 200 s. Output 3840x2160 in a 3840x2160 window, Reflex off, frame generation off. Every number is from a second run, on a warm driver cache.

Upscaler 3D fps median fps min GPU a frame
Game's own 67% (2560x1440) 49 38 11.9 ms
DLSS Quality, preset K 2560x1440 -> 3840x2160 45 17 13.6 ms
DLAA, preset K 3840x2160 41 34 15.9 ms
Game's own + RX_VK_WB_DIFF=1 67% 50 36 7.6 ms
DLSS Quality + RX_VK_WB_DIFF=1 2560x1440 -> 3840x2160 49.5 6 (one 1.5 s stall) 9.2 ms
  • DLSS itself costs about 1.6 ms of GPU here, and DLAA about 4 ms over the game's own at 67%. The scene is CPU-bound on this machine, so with the write-back diff on, DLSS Quality runs at the same frame rate as the game's own upscale.
  • About 4.5 ms of the GPU time in the first three rows is main's own: the v2.0.9 hotfix turned the write-back diff off (b784c68). RX_VK_PASS_TIMING=1 shows the same 68 MB a frame of wb copies taking 5.2 ms without the diff and 0.55 ms with it. It is unrelated to this PR. The diff ran 200 s twice on 616.92 without losing the device; I'll post that on #1161.
  • The log matches what the PR describes: NGX started, 2560x1440 (up to 3840x2160) -> 3840x2160, mode 2, preset K and DLAA as mode 5. The jitter changes from frame to frame, and the actor motion takes 47-111k draws per 600 frames at 0.11-0.20 ms of CPU a frame. No NGX errors.

Quality, still camera, 3840x2160 output. Same spawn view, screenshots at presents 2000-2002 (RX_VK_SHOT_W/H=3840x2160):

  • Mean frame-to-frame luma difference: game's own 0.71, DLSS Quality 0.29, DLAA 0.26. DLSS and DLAA shimmer about 2.5 times less.
  • Brightness matches across the three.
  • DLSS Quality is softer than the game's own network, which sharpens harder. DLAA is both clean and sharp.
  • With the jitter on, I saw no shaking of the whole picture and no shadow swimming on the still camera.

Merge with current main. The branch conflicts in gpu/vk/vk_menu.cpp, gpu/vk/vk_menu.h, readme.md and tests/run_all.py, mostly with #1144's 1920x1080 output (kOutputs, output_choice, step_output_choice). A local resolution that keeps this branch's rows works:

  • the Output resolution row's range becomes 0..kLastOutput, with its label from kOutputs;
  • the row step runs row_fixed, then step_output_choice, then the Upscaler and Render resolution steps;
  • HOS_OUTPUT_RES goes through output_choice, then restart_init().

After that, tests/test_output_resolution.py no longer compiles. It cuts vk_menu.cpp from struct Out { to tx_speed(, a span that now holds the upscaler code (opts.dlss, ur::, dlss_why), and it expects main's "(game, restart)" labels. upscaler_rows, menu_restart, menu_hit, dlss_math, dlss_actor, ngx_sdk, make_game_ngx and oot_jitter pass on the merged tree on Windows with llvm-mingw.

Not checked here: DLSS together with frame generation, Reflex latency, fullscreen on the display itself (the runs were in a background window), the update offer, and non-NVIDIA GPUs.

Test report from an RTX 4080 SUPER (Ada) at a 3840x2160 output, which covers the "with jitter, and DLAA" part of the quality item. **Setup.** RTX 4080 SUPER, driver 616.92, Windows 11, Ryzen 7 5800X3D, 64 GB. Built from de67a56 with llvm-mingw 20261104 and `scripts/ngx_sdk.py` (v310.9.16), with the 2.0.8 builder's hot list and PGO profile in `work/`. Kokiri Forest from a save at Saria's spot. An `HOS_INPUT_SCRIPT` with `HOS_INPUT_FRAME_CLOCK=1` loads it and swings the camera sideways for 200 s. Output 3840x2160 in a 3840x2160 window, Reflex off, frame generation off. Every number is from a second run, on a warm driver cache. | Upscaler | 3D | fps median | fps min | GPU a frame | |---|---|---|---|---| | Game's own | 67% (2560x1440) | 49 | 38 | 11.9 ms | | DLSS Quality, preset K | 2560x1440 -> 3840x2160 | 45 | 17 | 13.6 ms | | DLAA, preset K | 3840x2160 | 41 | 34 | 15.9 ms | | Game's own + `RX_VK_WB_DIFF=1` | 67% | 50 | 36 | 7.6 ms | | DLSS Quality + `RX_VK_WB_DIFF=1` | 2560x1440 -> 3840x2160 | 49.5 | 6 (one 1.5 s stall) | 9.2 ms | - DLSS itself costs about 1.6 ms of GPU here, and DLAA about 4 ms over the game's own at 67%. The scene is CPU-bound on this machine, so with the write-back diff on, DLSS Quality runs at the same frame rate as the game's own upscale. - About 4.5 ms of the GPU time in the first three rows is main's own: the v2.0.9 hotfix turned the write-back diff off (b784c68). `RX_VK_PASS_TIMING=1` shows the same 68 MB a frame of `wb copies` taking 5.2 ms without the diff and 0.55 ms with it. It is unrelated to this PR. The diff ran 200 s twice on 616.92 without losing the device; I'll post that on #1161. - The log matches what the PR describes: `NGX started`, `2560x1440 (up to 3840x2160) -> 3840x2160, mode 2, preset K` and DLAA as mode 5. The jitter changes from frame to frame, and the actor motion takes 47-111k draws per 600 frames at 0.11-0.20 ms of CPU a frame. No NGX errors. **Quality, still camera, 3840x2160 output.** Same spawn view, screenshots at presents 2000-2002 (`RX_VK_SHOT_W/H=3840x2160`): - Mean frame-to-frame luma difference: game's own 0.71, DLSS Quality 0.29, DLAA 0.26. DLSS and DLAA shimmer about 2.5 times less. - Brightness matches across the three. - DLSS Quality is softer than the game's own network, which sharpens harder. DLAA is both clean and sharp. - With the jitter on, I saw no shaking of the whole picture and no shadow swimming on the still camera. **Merge with current main.** The branch conflicts in `gpu/vk/vk_menu.cpp`, `gpu/vk/vk_menu.h`, `readme.md` and `tests/run_all.py`, mostly with #1144's 1920x1080 output (`kOutputs`, `output_choice`, `step_output_choice`). A local resolution that keeps this branch's rows works: - the Output resolution row's range becomes `0..kLastOutput`, with its label from `kOutputs`; - the row step runs `row_fixed`, then `step_output_choice`, then the Upscaler and Render resolution steps; - `HOS_OUTPUT_RES` goes through `output_choice`, then `restart_init()`. After that, `tests/test_output_resolution.py` no longer compiles. It cuts `vk_menu.cpp` from `struct Out {` to `tx_speed(`, a span that now holds the upscaler code (`opts.dlss`, `ur::`, `dlss_why`), and it expects main's "(game, restart)" labels. `upscaler_rows`, `menu_restart`, `menu_hit`, `dlss_math`, `dlss_actor`, `ngx_sdk`, `make_game_ngx` and `oot_jitter` pass on the merged tree on Windows with llvm-mingw. Not checked here: DLSS together with frame generation, Reflex latency, fullscreen on the display itself (the runs were in a background window), the update offer, and non-NVIDIA GPUs.
hckrman101 commented 2026-11-09 19:27:27 +00:00 (Migrated from github.com)

Thanks to everyone who tested this: the Windows reports on the RTX 5080, which caught the restart log loss, the shadow flicker and the start-up quirk; the RTX 3090 runs on Linux; and @itaskaev2's detailed 4080 SUPER report at 3840x2160. That one confirmed the jitter and shadows on a third card generation, measured DLSS at about 1.6 ms of GPU and DLAA at about 4 ms, and showed 2.5x less shimmer.

The last commits before the merge:

  • DLSS model row: Transformer (preset K), Transformer 2 (M/L) and NVIDIA's default. The pinned 310.9.16 accepts M.
  • A status line in the log every 600 frames: video memory used against the driver's allowance, plus DLSS's details while it draws, and a one-time warning when memory goes over. The readme has a note for 8 GB cards.
  • Window scaling filter: the start-up screen's frames no longer count as the game's picture, which fixes the "Not needed (window = output)" quirk at start-up.
  • Logging for stalls: a line each time DLSS makes its feature, saying why, and what a stall in play was.
  • Option ids: the gyro rows (#1114) took ids 18–22, so DLSS's jitter options moved to 24–26.

@itaskaev2, about the one 1.5 s stall in your DLSS Quality + RX_VK_WB_DIFF=1 run: I couldn't reproduce it here. The feature is only made again when the output size changes, the render size leaves the mode's range, the preset changes, or after the 120-frame idle release, and my runs made it exactly once. If you can run the same script again on v2.0.11 (or current main), the hookshot_log.txt lines around the stall would show which it was:

  • any vk: NVIDIA DLSS: feature <n> made (<reason>; ...) after the first one;
  • any not used for 120 frames ... let go line;
  • the vk: status: lines (their "the slowest" field, and "left to the game's network");
  • any the game's upscale runs (<reason>) line.

A 1.5 s slowest frame next to a "made" or "let go" line means DLSS made its feature again; frames left to the game's network point to a shader compile on the fallback path instead. Either way, a new issue is the best place for it now that this is merged.

Still untested, if anyone has the setup: DLSS together with frame generation, Reflex latency, true fullscreen, the DLSS update offer, and 8 GB cards.

Thanks to everyone who tested this: the Windows reports on the RTX 5080, which caught the restart log loss, the shadow flicker and the start-up quirk; the RTX 3090 runs on Linux; and @itaskaev2's detailed 4080 SUPER report at 3840x2160. That one confirmed the jitter and shadows on a third card generation, measured DLSS at about 1.6 ms of GPU and DLAA at about 4 ms, and showed 2.5x less shimmer. The last commits before the merge: - **DLSS model row:** Transformer (preset K), Transformer 2 (M/L) and NVIDIA's default. The pinned 310.9.16 accepts M. - **A status line in the log every 600 frames:** video memory used against the driver's allowance, plus DLSS's details while it draws, and a one-time warning when memory goes over. The readme has a note for 8 GB cards. - **Window scaling filter:** the start-up screen's frames no longer count as the game's picture, which fixes the "Not needed (window = output)" quirk at start-up. - **Logging for stalls:** a line each time DLSS makes its feature, saying why, and what a stall in play was. - **Option ids:** the gyro rows (#1114) took ids 18–22, so DLSS's jitter options moved to 24–26. @itaskaev2, about the one 1.5 s stall in your DLSS Quality + `RX_VK_WB_DIFF=1` run: I couldn't reproduce it here. The feature is only made again when the output size changes, the render size leaves the mode's range, the preset changes, or after the 120-frame idle release, and my runs made it exactly once. If you can run the same script again on v2.0.11 (or current main), the `hookshot_log.txt` lines around the stall would show which it was: - any `vk: NVIDIA DLSS: feature <n> made (<reason>; ...)` after the first one; - any `not used for 120 frames ... let go` line; - the `vk: status:` lines (their "the slowest" field, and "left to the game's network"); - any `the game's upscale runs (<reason>)` line. A 1.5 s slowest frame next to a "made" or "let go" line means DLSS made its feature again; frames left to the game's network point to a shader compile on the fallback path instead. Either way, a new issue is the best place for it now that this is merged. Still untested, if anyone has the setup: DLSS together with frame generation, Reflex latency, true fullscreen, the DLSS update offer, and 8 GB cards.
HypeZ85 commented 2026-11-09 19:37:38 +00:00 (Migrated from github.com)

PR-841-VALIDATION-REPORT-EN.md

codex found 2 issues in the code, attaching report

[PR-841-VALIDATION-REPORT-EN.md](https://github.com/user-attachments/files/34596640/PR-841-VALIDATION-REPORT-EN.md) codex found 2 issues in the code, attaching report
hckrman101 commented 2026-11-09 23:11:29 +00:00 (Migrated from github.com)

Thanks for the validation report. Both bugs are fixed in #1364:

  • Handheld + Ultra Performance now runs DLSS (512x288 -> 1536x864); the fixed 640x360 floor is replaced by the scene being 1 to 3 times smaller than the upscale's output.
  • Linux paths for NGX are decoded as UTF-8. Note that NVIDIA's NGX on Linux itself only loads its runtime from a folder whose real path is all ASCII. With the game in a folder like Café, put libnvidia-ngx-dlss.so.* in ~/.local/share/hookshot/ngx (or set RX_VK_DLSS_DIR); the log and menu now say this instead of "needs an RTX GPU".
Thanks for the validation report. Both bugs are fixed in https://git.magicbean.dev/MagicBeanDev/Hookshot/pulls/1364: - Handheld + Ultra Performance now runs DLSS (512x288 -> 1536x864); the fixed 640x360 floor is replaced by the scene being 1 to 3 times smaller than the upscale's output. - Linux paths for NGX are decoded as UTF-8. Note that NVIDIA's NGX on Linux itself only loads its runtime from a folder whose real path is all ASCII. With the game in a folder like `Café`, put `libnvidia-ngx-dlss.so.*` in `~/.local/share/hookshot/ngx` (or set `RX_VK_DLSS_DIR`); the log and menu now say this instead of "needs an RTX GPU".
Commenting is not possible because the repository is archived.
No description provided.