feat(vk): Anisotropic filtering option, 16x by default, and the upscalers' texture LOD bias #1898

Merged
hckrman101 merged 3 commits from anisotropic-filtering into main 2026-11-10 19:53:07 +00:00
hckrman101 commented 2026-11-10 19:48:18 +00:00 (Migrated from github.com)

Paths and floors in front of Link go muddy a short way ahead: the game asks for 2x anisotropic filtering at most (1x on some samplers), and nothing raised it unless RX_VK_ANISO was set by hand. This adds an Anisotropic filtering option, 16x by default, and a small extra texture LOD bias while DLSS or FSR renders below the game's own 3/4.

Kokiri Forest path, same view at 2560x1440. Top: the game's anisotropy. Middle: 16x. Bottom: 16x plus the upscaler bias.

anisotropy

The upscaler bias with DLSS (off, then on)

DLSS Performance:

DLSS Performance

DLSS Quality (barely visible, as expected at 67%):

DLSS Quality

What the game asks for

A new log line records each sampler kind the game makes:

  • linear-mip samplers: 2x anisotropy, LOD bias -0.5;
  • nearest-mip samplers: 1x, some with -0.5;
  • post-processing and UI: 0 or 1x, bias 0.

The -0.5 is the game compensating for its own 3D, which it draws at 3/4 of the output.

Anisotropic filtering row

A Graphics row after Sharpness:

  • Values: Game's / 2x / 4x / 8x / 16x. The default is 16x, capped at the GPU's limit, saved as anisotropy=.
  • Coverage: every linear sampler with mips, the game's nearest-mip ones included. Before, RX_VK_ANISO only touched linear-mip samplers.
  • Takes effect at once: a new samplers_changed() (vk_bank.cpp) drops the outer sampler table and reused descriptors.
  • Override: RX_VK_ANISO=<n> still overrides the row for a run (0: the game's).

Upscaler LOD bias

While DLSS, FSR 3.1 or FSR 4 renders at a fixed share below 3/4, the samplers the game already biases below 0 get an extra log2(share / 0.75):

Render resolution Extra bias
Quality -0.17
Balanced -0.37
Performance -0.58
Ultra Performance -1.17

Nothing changes with the game's own upscaler, DLAA, FSR 4 Native AA, Auto, or 75% and up.

  • Applies at once: the bias is part of the sampler key, so changing Upscaler, Render resolution or Quality preset makes the samplers again.
  • Off switch: RX_VK_UPSCALE_LOD_BIAS=0.

Cost

On an RTX 3090 at 2560x1440 (two alternating rounds, the game's GPU-time log line), 16x costs nothing measurable:

Upscaler Game's anisotropy 16x
DLSS Quality 11.53 / 11.67 ms 11.37 / 11.40 ms
DLAA 13.52 / 13.05 ms 13.02 / 13.45 ms

Tests

  • tests/test_sampler_admission.py: all three routes pass, 135 cases. New cases cover:
    • the row, the GPU-limit clamp, Game's, and the environment variable over the row;
    • a row change making the sampler again;
    • nearest-mip samplers getting anisotropy, and samplers without mips not;
    • the upscaler bias applying only to biased samplers, and following a change.
  • Other tests: test_framegen_preset.py and test_rumble_menu.py get a samplers_changed stub.
  • Full suite: tests/run_all.py on Linux gives the same results as main.

Not changed here

DLSS's own output is clearly softer than the game's upscaler at the same 67%: edge energy on the path is about 10 against 17. That's the bigger part of a soft picture with DLSS, and it's left for a separate look.

Paths and floors in front of Link go muddy a short way ahead: the game asks for 2x anisotropic filtering at most (1x on some samplers), and nothing raised it unless `RX_VK_ANISO` was set by hand. This adds an Anisotropic filtering option, 16x by default, and a small extra texture LOD bias while DLSS or FSR renders below the game's own 3/4. Kokiri Forest path, same view at 2560x1440. Top: the game's anisotropy. Middle: 16x. Bottom: 16x plus the upscaler bias. ![anisotropy](https://github.com/hckrman101/Hookshot/raw/pr-assets/aniso/aniso-vs-game.png) <details><summary>The upscaler bias with DLSS (off, then on)</summary> DLSS Performance: ![DLSS Performance](https://github.com/hckrman101/Hookshot/raw/pr-assets/aniso/bias-dlss-perf.png) DLSS Quality (barely visible, as expected at 67%): ![DLSS Quality](https://github.com/hckrman101/Hookshot/raw/pr-assets/aniso/bias-dlss-quality.png) </details> ## What the game asks for A new log line records each sampler kind the game makes: - linear-mip samplers: 2x anisotropy, LOD bias -0.5; - nearest-mip samplers: 1x, some with -0.5; - post-processing and UI: 0 or 1x, bias 0. The -0.5 is the game compensating for its own 3D, which it draws at 3/4 of the output. ## Anisotropic filtering row A Graphics row after Sharpness: - **Values:** Game's / 2x / 4x / 8x / 16x. The default is 16x, capped at the GPU's limit, saved as `anisotropy=`. - **Coverage:** every linear sampler with mips, the game's nearest-mip ones included. Before, `RX_VK_ANISO` only touched linear-mip samplers. - **Takes effect at once:** a new `samplers_changed()` (`vk_bank.cpp`) drops the outer sampler table and reused descriptors. - **Override:** `RX_VK_ANISO=<n>` still overrides the row for a run (0: the game's). ## Upscaler LOD bias While DLSS, FSR 3.1 or FSR 4 renders at a fixed share below 3/4, the samplers the game already biases below 0 get an extra `log2(share / 0.75)`: | Render resolution | Extra bias | | --- | --- | | Quality | -0.17 | | Balanced | -0.37 | | Performance | -0.58 | | Ultra Performance | -1.17 | Nothing changes with the game's own upscaler, DLAA, FSR 4 Native AA, Auto, or 75% and up. - **Applies at once:** the bias is part of the sampler key, so changing Upscaler, Render resolution or Quality preset makes the samplers again. - **Off switch:** `RX_VK_UPSCALE_LOD_BIAS=0`. ## Cost On an RTX 3090 at 2560x1440 (two alternating rounds, the game's GPU-time log line), 16x costs nothing measurable: | Upscaler | Game's anisotropy | 16x | | --- | --- | --- | | DLSS Quality | 11.53 / 11.67 ms | 11.37 / 11.40 ms | | DLAA | 13.52 / 13.05 ms | 13.02 / 13.45 ms | ## Tests - **`tests/test_sampler_admission.py`:** all three routes pass, 135 cases. New cases cover: - the row, the GPU-limit clamp, Game's, and the environment variable over the row; - a row change making the sampler again; - nearest-mip samplers getting anisotropy, and samplers without mips not; - the upscaler bias applying only to biased samplers, and following a change. - **Other tests:** `test_framegen_preset.py` and `test_rumble_menu.py` get a `samplers_changed` stub. - **Full suite:** `tests/run_all.py` on Linux gives the same results as `main`. ## Not changed here DLSS's own output is clearly softer than the game's upscaler at the same 67%: edge energy on the path is about 10 against 17. That's the bigger part of a soft picture with DLSS, and it's left for a separate look.
Commenting is not possible because the repository is archived.
No description provided.