feat: an Upscaler choice Off: no upscaling at all, the game's own network left out too #2097

Merged
hckrman101 merged 1 commit from upscaler-off into main 2026-11-11 02:59:20 +00:00
hckrman101 commented 2026-11-11 02:31:12 +00:00 (Migrated from github.com)

Low-end PCs, and automated runs that only need the game to go fast, have no way to turn upscaling off: even with DLSS and FSR off, the game's own upscale network runs on every frame. This adds Off (no upscaling) to the Upscaler row: no DLSS, no FSR, and the game's own upscale pass is skipped too.

What changes

  • Upscaler row: a new choice, Off (no upscaling), after AMD FSR 4. It is saved as dlss=6, so the values saved before keep their numbers. Off runs on every PC, so the row is now always shown (before, it was hidden where only the game's own could run).
  • With Off: the game's gameplay upscale network is not dispatched: fd029a317a9a4b94 at the usual 3/4 share, the same-size pass 9dc98871eb7fc101 at 100%, and the networks for each fixed share. Its input is stretched straight into its output with a linear filter and squared back to linear colour. That is shaders/dlss_out.comp, the last pass DLSS and FSR 4 already use, so there is no new shader; at the same size it is a copy. These dispatches are caught where DLSS and FSR 4 catch them (temporal_stand_in, and upscale_stub for networks that do not translate). A network not known by its key is only replaced when its input is this frame's scene, as for DLSS. Off has no camera jitter, no history, no motion vectors and no extra LOD bias, so there is nothing to ghost and nothing left over when you switch back. The HUD is drawn over the result at the output's size, as before. Movies are not affected.
  • Render resolution with Off: this row alone sets the 3D's size, with its own steps: Auto, 100%, 75%, 67%, 50% and 33%. There is no network per share any more, so any size is stretched the same way. Switching to Off moves the value to its nearest step.
  • RX_VK_DLSS, RX_VK_FSR4 and RX_VK_FSR set for a run still take priority over the row, as they do for its other choices.
  • Readme: the options table, the scaling-rows paragraph and a new Upscaler Off note.
  • Tests: tests/test_upscaler_rows.cpp checks Off's value, its place in the row (step_upscaler / step_engine), its Render resolution steps (step, snap, step_render, snap_render) and that the row is always shown. tests/test_upscaler_rows.py checks that the menu offers and shows Off, and that temporal_off defers to an engine set for the run. It also checks that the stand-ins, temporal_upscale and the scene camera handle Off, and that the stretch only takes the scene into the picture. tests/test_upscale_fallback.py gets a stub for temporal_off.

Measured

RTX 3090 on Linux, Output resolution 3840x2160, frame rate cap 240, Sharpness 0, Window scaling filter Off. The user's Hyrule Field save, Link standing on the path to the field. Each row is one exclusive run of about 35 s of play; GPU time is the renderer's own vk: GPU time (the median of the last six 120-frame windows). The runs were headless, so fps is held near 60 by the CPU side and GPU time is the number to compare.

Upscaler Render resolution (3D size) GPU time per frame fps
Game's own 100% (3840x2160) 16.2 ms 50
Off 100% (3840x2160) 16.0 ms 48
Game's own 75% (2880x1620) 13.6 ms 58
Off 75% (2880x1620) 13.35 ms 60
NVIDIA DLSS Performance, 50% (1920x1080) 13.2 ms 60
Off 50% (1920x1080) 10.7 ms 70

On this card the game's network is cheap: it runs on the tensor cores and costs about 0.25 ms at 4K. So Off at the same size saves little here; it will save more on GPUs that run the network's matrix multiplies in software. The gain is the smaller sizes: Off at 50% is 2.5 ms (19%) faster than DLSS Performance at the same 3D size, and 2.9 ms faster than the default 75% with the game's own. The picture is rougher: no edge smoothing, and soft below 100%.

Screenshots

Captured at 3840x2160 (shown here scaled to 1920 wide; the crops at the end are at full detail), same spot, each from one of the runs above:

Game's own, 75%

Game's own, 75%

Off, 75%

Off, 75%

NVIDIA DLSS Performance

NVIDIA DLSS Performance

Off, 50%

Off, 50%

Off, 33% (1280x720 stretched; from a shared check run on the rebased build, 10.3 ms GPU time there, not a timed number)

Off, 33% (1280x720 stretched; from a shared check run on the rebased build, 10.3 ms GPU time there, not a timed number)

Detail crops of all six side by side

Detail crops of all six side by side

Not tested

  • AMD or Intel GPUs, and GPUs without cooperative matrices. There the fixed shares' networks do not translate and upscale_stub hands them to the same stretch; this path is covered by the game-free checks only.
  • Windows (the change is renderer-only and portable).
  • Auto (dynamic resolution) with Off was not measured.
  • Stepping through the row in the menu was not done by hand; it is covered by the row tests.
Low-end PCs, and automated runs that only need the game to go fast, have no way to turn upscaling off: even with DLSS and FSR off, the game's own upscale network runs on every frame. This adds **Off (no upscaling)** to the Upscaler row: no DLSS, no FSR, and the game's own upscale pass is skipped too. ## What changes - **Upscaler row:** a new choice, **Off (no upscaling)**, after AMD FSR 4. It is saved as `dlss=6`, so the values saved before keep their numbers. Off runs on every PC, so the row is now always shown (before, it was hidden where only the game's own could run). - **With Off:** the game's gameplay upscale network is not dispatched: `fd029a317a9a4b94` at the usual 3/4 share, the same-size pass `9dc98871eb7fc101` at 100%, and the networks for each fixed share. Its input is stretched straight into its output with a linear filter and squared back to linear colour. That is `shaders/dlss_out.comp`, the last pass DLSS and FSR 4 already use, so there is no new shader; at the same size it is a copy. These dispatches are caught where DLSS and FSR 4 catch them (`temporal_stand_in`, and `upscale_stub` for networks that do not translate). A network not known by its key is only replaced when its input is this frame's scene, as for DLSS. Off has no camera jitter, no history, no motion vectors and no extra LOD bias, so there is nothing to ghost and nothing left over when you switch back. The HUD is drawn over the result at the output's size, as before. Movies are not affected. - **Render resolution with Off:** this row alone sets the 3D's size, with its own steps: Auto, 100%, 75%, 67%, 50% and 33%. There is no network per share any more, so any size is stretched the same way. Switching to Off moves the value to its nearest step. - `RX_VK_DLSS`, `RX_VK_FSR4` and `RX_VK_FSR` set for a run still take priority over the row, as they do for its other choices. - Readme: the options table, the scaling-rows paragraph and a new **Upscaler Off** note. - Tests: `tests/test_upscaler_rows.cpp` checks Off's value, its place in the row (`step_upscaler` / `step_engine`), its Render resolution steps (`step`, `snap`, `step_render`, `snap_render`) and that the row is always shown. `tests/test_upscaler_rows.py` checks that the menu offers and shows Off, and that `temporal_off` defers to an engine set for the run. It also checks that the stand-ins, `temporal_upscale` and the scene camera handle Off, and that the stretch only takes the scene into the picture. `tests/test_upscale_fallback.py` gets a stub for `temporal_off`. ## Measured RTX 3090 on Linux, Output resolution 3840x2160, frame rate cap 240, Sharpness 0, Window scaling filter Off. The user's Hyrule Field save, Link standing on the path to the field. Each row is one exclusive run of about 35 s of play; GPU time is the renderer's own `vk: GPU time` (the median of the last six 120-frame windows). The runs were headless, so fps is held near 60 by the CPU side and GPU time is the number to compare. | Upscaler | Render resolution (3D size) | GPU time per frame | fps | |---|---|---|---| | Game's own | 100% (3840x2160) | 16.2 ms | 50 | | **Off** | 100% (3840x2160) | 16.0 ms | 48 | | Game's own | 75% (2880x1620) | 13.6 ms | 58 | | **Off** | 75% (2880x1620) | 13.35 ms | 60 | | NVIDIA DLSS | Performance, 50% (1920x1080) | 13.2 ms | 60 | | **Off** | 50% (1920x1080) | **10.7 ms** | 70 | On this card the game's network is cheap: it runs on the tensor cores and costs about 0.25 ms at 4K. So Off at the same size saves little here; it will save more on GPUs that run the network's matrix multiplies in software. The gain is the smaller sizes: Off at 50% is 2.5 ms (19%) faster than DLSS Performance at the same 3D size, and 2.9 ms faster than the default 75% with the game's own. The picture is rougher: no edge smoothing, and soft below 100%. ## Screenshots Captured at 3840x2160 (shown here scaled to 1920 wide; the crops at the end are at full detail), same spot, each from one of the runs above: **Game's own, 75%** ![Game's own, 75%](https://github.com/hckrman101/Hookshot/raw/pr-assets/upscale-off/game75.jpg) **Off, 75%** ![Off, 75%](https://github.com/hckrman101/Hookshot/raw/pr-assets/upscale-off/off75.jpg) **NVIDIA DLSS Performance** ![NVIDIA DLSS Performance](https://github.com/hckrman101/Hookshot/raw/pr-assets/upscale-off/dlssP.jpg) **Off, 50%** ![Off, 50%](https://github.com/hckrman101/Hookshot/raw/pr-assets/upscale-off/off50.jpg) **Off, 33% (1280x720 stretched; from a shared check run on the rebased build, 10.3 ms GPU time there, not a timed number)** ![Off, 33% (1280x720 stretched; from a shared check run on the rebased build, 10.3 ms GPU time there, not a timed number)](https://github.com/hckrman101/Hookshot/raw/pr-assets/upscale-off/off33.jpg) **Detail crops of all six side by side** ![Detail crops of all six side by side](https://github.com/hckrman101/Hookshot/raw/pr-assets/upscale-off/crops.jpg) ## Not tested - AMD or Intel GPUs, and GPUs without cooperative matrices. There the fixed shares' networks do not translate and `upscale_stub` hands them to the same stretch; this path is covered by the game-free checks only. - Windows (the change is renderer-only and portable). - Auto (dynamic resolution) with Off was not measured. - Stepping through the row in the menu was not done by hand; it is covered by the row tests.
Commenting is not possible because the repository is archived.
No description provided.