Skip to content

feat(vk): FSR 1 EASU + RCAS in place of the bilinear TAAU stand-in - #1439

Open
kafasam wants to merge 2 commits into
NotRexed:mainfrom
kafasam:fsr1-easu-rcas
Open

kafasam wants to merge 2 commits into
NotRexed:mainfrom
kafasam:fsr1-easu-rcas

Conversation

@kafasam

@kafasam kafasam commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

What

On AMD, the game's TAAU neural upscaler is replaced by a bilinear stand-in (HMMA cost issue #52). This replaces that bilinear with FSR 1's EASU (12-tap edge-adaptive) + RCAS (contrast-capped sharpening) as two GLSL compute shaders.

Why

Bilinear at 50% internal scale loses all edge detail — foliage smears, distant geometry loses definition. EASU preserves edges and detail at the same resolution with ~0.5ms GPU cost (measured on RX 6700 XT). The EASU/RCAS shader code was already in the repo (inherited from TOTKPort in gpu/render/rx_present_fx.cpp) — this ports it to GLSL compute and wires it into the existing stand-in site.

Changes

  • gpu/vk/shaders/taau_easu.comp (new) — FSR 1 EASU, 8×8 workgroups, reads RGB9E5, writes B10G11R11
  • gpu/vk/shaders/taau_rcas.comp (new) — RCAS with the stand-in's flash/NaN history clamp preserved
  • gpu/vk/vk_draw.cpp — typed_upscale builds three pipelines off one push-descriptor layout; EASU → barrier → RCAS
  • SPIR-V headers compiled with glslang 16.6.0 (push-constant offsets verified in disassembly)
  • Escape hatches: RX_VK_TAAU_MODE=bilinear (old path), RX_VK_TAAU_SHARPNESS=<stops> (default 0.2)

Testing

  • Built clean (0 failures) on the proven recipe (llvm-mingw, 599 files)
  • Smoke-tested on RX 6700 XT: alive 22+ min, save loaded, stable GPU stats, clean exit
  • Played through Hyrule Field and graveyard scenes: visually sharper than bilinear (especially foliage edges, distant detail), no artifacts
  • 25→28 fps in the graveyard (though this build also includes RX_VK_ASYNC=1 — the EASU itself costs ~0.5ms)
  • Old stand-in untouched — revert with RX_VK_TAAU_MODE=bilinear

Note

This is quality-only — it won't raise fps in CPU-bound scenes (the frame is guest-emulation-bound). But it gives the 50% Resolution row a proper upscaler instead of a plain blit, at essentially zero GPU cost.

AI assistance disclosure: the EASU/RCAS port and pipeline wiring were developed with AI assistance, verified and tested by a human on the affected hardware.

@kafasam

kafasam commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Tested on RX 6700 XT (RDNA2) at 4K/50% scale: 28-33 fps with the EASU+RCAS active (also running RX_VK_ASYNC=1 + RX_VK_BATCH=1 + RX_VK_PRESENT_QUEUE=1). The upscaler costs ~0.5ms GPU (measured 16.1ms/frame with EASU vs 15.5ms with bilinear). Visually: foliage edges and distant detail are noticeably sharper than bilinear. No artifacts observed. A/B revert via RX_VK_TAAU_MODE=bilinear works as expected.

kafasam pushed a commit to kafasam/Hookshot that referenced this pull request Nov 10, 2026
…olour space)

Review on PR NotRexed#1439 (hckrman101), all four addressed:

- taau_rcas.comp: the history bound tests (pc.valid & 1) only, as main's
  taau_upscale.comp after PR NotRexed#1115; the higher bits carry FXAA there and must
  never turn the bound on (first frame after a resize clamped against an
  unwritten hist).
- vk_draw.cpp, readme: the FSR path replaces NotRexed#1115's FXAA, documented at the
  mode decision and in the readme's switches: RX_VK_TAAU_FXAA applies only
  with RX_VK_TAAU_MODE=bilinear; EASU's kernel does the edge work otherwise.
- taau_easu/taau_rcas.comp: the sqrt decoding moved out of EASU's loads to a
  single square at RCAS's output (before the bound): FSR 1 runs on the
  perceptual encoding, its edge detection, luma weights and RCAS's 1 - mx4
  term ([0,1]) now work in the space they were tuned for, and mid's B10G11R11
  keeps more of the darks. spv headers regenerated (glslang 16.6.0).
- tests/test_taau_env_lookup.py: the fixture's spies match the stand-in's
  current shape: ViewUse::Sample and VK_FORMAT_UNDEFINED in the spy, a
  view_type stub, Texture width/height, a 10-arg typed_upscale spy that
  checks the forwarded source size, and a no-resolved-input rejection case.
kafasam pushed a commit to kafasam/Hookshot that referenced this pull request Nov 10, 2026
…olour space)

Review on PR NotRexed#1439 (hckrman101), all four addressed:

- taau_rcas.comp: the history bound tests (pc.valid & 1) only, as main's
  taau_upscale.comp after PR NotRexed#1115; the higher bits carry FXAA there and must
  never turn the bound on (first frame after a resize clamped against an
  unwritten hist).
- vk_draw.cpp, readme: the FSR path replaces NotRexed#1115's FXAA, documented at the
  mode decision and in the readme's switches: RX_VK_TAAU_FXAA applies only
  with RX_VK_TAAU_MODE=bilinear; EASU's kernel does the edge work otherwise.
- taau_easu/taau_rcas.comp: the sqrt decoding moved out of EASU's loads to a
  single square at RCAS's output (before the bound): FSR 1 runs on the
  perceptual encoding, its edge detection, luma weights and RCAS's 1 - mx4
  term ([0,1]) now work in the space they were tuned for, and mid's B10G11R11
  keeps more of the darks. spv headers regenerated (glslang 16.6.0).
- tests/test_taau_env_lookup.py: the fixture's spies match the stand-in's
  current shape: ViewUse::Sample and VK_FORMAT_UNDEFINED in the spy, a
  view_type stub, Texture width/height, a 10-arg typed_upscale spy that
  checks the forwarded source size, and a no-resolved-input rejection case.
@kafasam

kafasam commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough re-review! All four fixes are verified — I've addressed the two merge blockers:

  1. Unrelated commits removed: the branch now contains only the two EASU commits (the shader work + the review fixes). The readback flush (26b9466) and memory leak fix (8fb0290) are split into separate branches (readback-flush-only, memory-leak-only) and will be submitted as their own PRs.

  2. Rebase: I'll rebase onto current main (which now includes feat(vk): FXAA in the gameplay TAAU stand-in (edges on cards without the network) #1115 and the v2.0.12 changes) once the branch split is confirmed clean. Will push the rebased version shortly.

The forced update is already on fsr1-easu-rcas — the branch is now exactly the EASU work and nothing else.

@kafasam

kafasam commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto v2.0.14 and split — this branch now contains only the two EASU commits on top of current main. The readback flush and memory leak fix are submitted as #1905 and #1906.

@kafasam
kafasam force-pushed the fsr1-easu-rcas branch 2 times, most recently from 7f70b92 to b1009cc Compare November 10, 2026 19:59
kafasam pushed a commit to kafasam/Hookshot that referenced this pull request Nov 11, 2026
…olour space)

Review on PR NotRexed#1439 (hckrman101), all four addressed:

- taau_rcas.comp: the history bound tests (pc.valid & 1) only, as main's
  taau_upscale.comp after PR NotRexed#1115; the higher bits carry FXAA there and must
  never turn the bound on (first frame after a resize clamped against an
  unwritten hist).
- vk_draw.cpp, readme: the FSR path replaces NotRexed#1115's FXAA, documented at the
  mode decision and in the readme's switches: RX_VK_TAAU_FXAA applies only
  with RX_VK_TAAU_MODE=bilinear; EASU's kernel does the edge work otherwise.
- taau_easu/taau_rcas.comp: the sqrt decoding moved out of EASU's loads to a
  single square at RCAS's output (before the bound): FSR 1 runs on the
  perceptual encoding, its edge detection, luma weights and RCAS's 1 - mx4
  term ([0,1]) now work in the space they were tuned for, and mid's B10G11R11
  keeps more of the darks. spv headers regenerated (glslang 16.6.0).
- tests/test_taau_env_lookup.py: the fixture's spies match the stand-in's
  current shape: ViewUse::Sample and VK_FORMAT_UNDEFINED in the spy, a
  view_type stub, Texture width/height, a 10-arg typed_upscale spy that
  checks the forwarded source size, and a no-resolved-input rejection case.
@kafasam

kafasam commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (4d68bc8) per review, as commit c3728dc. The readme change applied clean; gpu/vk/vk_draw.cpp needed conflict resolution, as on the v2.1.0-rc.3 rebase:

  • forward declarations: main's temporal_rejected_network kept, typed_upscale carries the new src_w/src_h params;
  • the generic RGB9E5 call site: main's input_view && output_view null-checks kept, source size passed through;
  • the dispatch: main's FXAA bit computation kept but gated to the bilinear branch only (if (bilinear && temporal && fxaa)) — the EASU push-constant's valid field expects 0/1 and EASU's edge-adaptive kernel replaces FXAA; the bilinear/pipe_linear vs EASU pipe_easu+pipe_rcas split with the mid image is unchanged;
  • readme: both sides' paragraphs kept (RX_VK_WB_MAX_KB, RX_VK_TAAU_MODE/SHARPNESS).

Update: the two Python test suites pass against the rebased tree with the llvm-mingw toolchain — test_upscale_fallback.py (all 23 cases) and test_taau_env_lookup.py.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The c3728dc rebase is good: gpu/vk/vk_draw.cpp and readme.md merge cleanly with current main. The only conflict left is tests/test_taau_env_lookup.py, where #2042 rewrote the file to include hookshot_env.h and wrap env_on. Keep main's version and add your src_w/src_h spy changes on top. At the PR head, test_taau_fxaa, test_taau_env_lookup and test_upscale_fallback pass locally.

The blocker is CI. The title "gpu/vk: FSR 1 ..." fails the Conventional Commit check, so only that check has run, and the Windows and llvm-mingw builds of the new shaders have never been run. Something like feat(vk): FSR 1 EASU + RCAS in place of the bilinear TAAU stand-in would pass.

One thing to note before merge: taau_key picks the stand-in wherever mma_software is set (vk_coopmat.cpp). That covers AMD, Intel, the GTX 16xx cards and the Steam Deck, so FSR 1 replaces FXAA by default on every one of them. You measured 0.5 ms on a 6700 XT. A timing on the Deck or on an Intel iGPU would show the default is safe at the low end.

Hookshot Builder and others added 2 commits November 11, 2026 19:41
The bilinear stand-in on AMD loses all edge detail at 50% internal scale.
This replaces it with FSR 1's EASU (12-tap edge-adaptive) and RCAS
(5-tap contrast-capped sharpening) as two GLSL compute shaders wired
into the existing taau_stand_in/typed_upscale interception.

- gpu/vk/shaders/taau_easu.comp: EASU ported from the WGSL in
  rx_present_fx.cpp, 8x8 workgroups, reads RGB9E5, writes B10G11R11
- gpu/vk/shaders/taau_rcas.comp: RCAS with the stand-in's flash/NaN
  history clamp preserved
- gpu/vk/vk_draw.cpp: typed_upscale builds three pipelines (sampler/
  mid/dst/hist), EASU then RCAS dispatch with full_barrier between
- Escape hatches: RX_VK_TAAU_MODE=bilinear (old path),
  RX_VK_TAAU_SHARPNESS=<stops> (default 0.2)
- Measured on RX 6700 XT: ~0.5ms GPU cost, visually sharper than
  bilinear at the same resolution

Co-authored-by: GLM-5.3 <noreply@z.ai>
…olour space)

Review on PR NotRexed#1439 (hckrman101), all four addressed:

- taau_rcas.comp: the history bound tests (pc.valid & 1) only, as main's
  taau_upscale.comp after PR NotRexed#1115; the higher bits carry FXAA there and must
  never turn the bound on (first frame after a resize clamped against an
  unwritten hist).
- vk_draw.cpp, readme: the FSR path replaces NotRexed#1115's FXAA, documented at the
  mode decision and in the readme's switches: RX_VK_TAAU_FXAA applies only
  with RX_VK_TAAU_MODE=bilinear; EASU's kernel does the edge work otherwise.
- taau_easu/taau_rcas.comp: the sqrt decoding moved out of EASU's loads to a
  single square at RCAS's output (before the bound): FSR 1 runs on the
  perceptual encoding, its edge detection, luma weights and RCAS's 1 - mx4
  term ([0,1]) now work in the space they were tuned for, and mid's B10G11R11
  keeps more of the darks. spv headers regenerated (glslang 16.6.0).
- tests/test_taau_env_lookup.py: the fixture's spies match the stand-in's
  current shape: ViewUse::Sample and VK_FORMAT_UNDEFINED in the spy, a
  view_type stub, Texture width/height, a 10-arg typed_upscale spy that
  checks the forwarded source size, and a no-resolved-input rejection case.
@kafasam kafasam changed the title gpu/vk: FSR 1 EASU+RCAS upscale replacing the bilinear TAAU stand-in feat(vk): FSR 1 EASU + RCAS in place of the bilinear TAAU stand-in Nov 12, 2026
@kafasam

kafasam commented Nov 12, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (ef81aee) as asked; the head is now 4876761, still the same two commits.

Conflict resolved: only tests/test_taau_env_lookup.py, as you said. I kept main's version from #2042 (the hookshot_env.h include through -include / /FI, and the env_on wrapper over the fixture's environment) and put this PR's spy changes on top: the src_w/src_h arguments of typed_upscale, the input texture resolved through ViewUse::Sample, and the "no resolved input texture" rejection. The branch's copy of that file had CRLF line endings where main's is LF, which is why the whole file showed as conflicting; it is LF now, and the diff against main for that file is +21/-9.

gpu/vk/vk_draw.cpp and readme.md applied without conflict. main's changes in the TAAU code since the last rebase were the getenv to env_on conversions (RX_VK_TAAU_CLAMP, RX_VK_TAAU_HIST_LEAK), and they are kept as main has them. No conflict markers anywhere in the diff.

Tests at 4876761, llvm-mingw clang++ (x86_64-w64-mingw32) on Windows:

  • tests/test_upscale_fallback.py: pass
  • tests/test_taau_env_lookup.py: pass (seven key configurations; rejected lookups removed, eligible per-call forwarding preserved)
  • tests/test_taau_fxaa.py: pass

Title: changed to feat(vk): FSR 1 EASU + RCAS in place of the bilinear TAAU stand-in, your suggestion, so the Conventional Commit check passes and the builds can run. The Windows and llvm-mingw builds of the new shaders still have not run in CI.

Not done: the timing on a Steam Deck or an Intel iGPU. I only have the 6700 XT figure, so whether FSR 1 is a safe default at the low end (everywhere mma_software is set) is still open.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points from my last review are done: the title passes, and 4876761 merges cleanly with current main. I merged it into main and ran tests/run_all.py: 426 passed, and the only failure is test_lift_cache, which fails on main here too. test_taau_env_lookup, test_taau_fxaa and test_upscale_fallback all pass. CI's builds still haven't run because the workflows are waiting for approval, so the llvm-mingw build of the two new shaders is still unchecked.

The point still open is the default. typed_upscale now runs EASU+RCAS on every mma_software card (AMD, Intel, the GTX 16xx cards and the Deck), and in the generic RGB9E5 fallback too. That replaces #1115's FXAA on the strength of one 6700 XT timing. A number from a Deck or an Intel iGPU would settle it. Otherwise, keep FSR 1 opt-in until someone has one.

Smaller things:

  • If pipe_easu or pipe_rcas fails to build, typed_upscale returns false and the network runs. Falling back to pipe_linear would keep the old stand-in instead.
  • No test compares taau_easu_comp_spv.h or taau_rcas_comp_spv.h with their sources, the way test_taau_fxaa does for taau_upscale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants