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.

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, this is a nice upgrade over the plain linear stand-in. I checked the EASU and RCAS math against ffx_fsr1.h and it matches: the tap offsets, the easu_set weights, lob/clp, the zero-direction case, the min4/max4 de-ring clamp, and RCAS's 0.1875 limit with exp2(-stops) sharpness. The pipeline wiring looks right to me. All 4 bindings are pushed before both dispatches, the 24-byte push range covers both shaders and still fits the old 12-byte block, the EASU dispatch bumps the work epoch so the full_barrier between the passes doesn't get deduped, and mid gets resized and retired the same way hist does.

I'm leaving this as a comment rather than an approval because of the rebase. A couple of things need deciding there:

  1. Conflict with #1115 (merged today). Main's taau_upscale.comp now runs FXAA in the gameplay stand-in, on by default. pc.valid is now a bitfield: 1 = history bound, 2 = FXAA, 4 = debug colours, and typed_upscale ORs FXAA's bits into size[2]. Once you rebase, the RCAS check pc.valid != 0 would turn the history clamp on every frame, including the first frame after a resize when hist is still undefined (see inline). You also need to decide how EASU and FXAA relate. EASU+RCAS keeps edges sharp but doesn't remove stair-steps, which is the shimmer #1115 was fixing. Options: make EASU the default with a note that RX_VK_TAAU_MODE=bilinear brings back the FXAA path, run FXAA before EASU, or keep FXAA as the default and make EASU opt-in. Whichever you pick, RX_VK_TAAU_FXAA should still mean something, and tests/test_taau_fxaa.py has to keep passing.
  2. tests/test_taau_env_lookup.py will break. It's registered in run_all.py. It pulls taau_stand_in out of the real vk_draw.cpp and compiles it against spies: an 8-arg typed_upscale, and a texture_view_of that only accepts ViewUse::Storage. The fixture has no view_type, ViewUse::Sample, VK_FORMAT_UNDEFINED, Desc::type or Texture::width, so with this PR it won't compile. And even with those stubs added, the input lookup would return null and every accepted case would fail. Please update the fixture as part of this PR.

How this fits with #1008 and the other FSR PRs: I don't see a runtime clash. #1008's temporal_stand_in (and #1386's fsr3_stand_in) run before taau_stand_in in the dispatch path and take the network whenever their engine is selected and can run. This PR only changes what runs when the Upscaler row is on Game, or when the engine can't run on cards that leave the network out. So FSR 1 is the fallback underneath FSR 4/3.1, not a rival to them. Where they will collide is in vk_draw.cpp text: #1008 edits the lines right around typed_upscale/taau_stand_in, so whichever lands second has a small rebase. Naming-wise, it's good that this stays env-only and doesn't add an "FSR" choice to the Upscaler row, because #1008's "FSR" means FSR 4. #1386 also vendors ffx_fsr1.h. If that lands first, it might be worth including from there instead of keeping a hand port, but that's not a blocker.

No settings option ids are added here, so nothing collides. CI reports no checks on this branch.

Comment thread gpu/vk/shaders/taau_rcas.comp Outdated
const vec3 lobeRGB = max(-hitMin, hitMax);
const float lobe = max(-0.1875, min(max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)), 0.0)) * pc.sharp;
vec3 c = (lobe * (b + d + h + f) + e) / (4.0 * lobe + 1.0);
if (pc.valid != 0) { // taau_upscale.comp's bound, unchanged

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

After rebasing onto #1115, size[2] carries FXAA's bits (2, or 6 with RX_VK_TAAU_FXAA=2) on the temporal call, so pc.valid != 0 would be true every frame. That turns the clamp on even though RX_VK_TAAU_CLAMP is off by default, and on the first frame after a resize it clamps against a hist that was never written. This should be (pc.valid & 1) != 0, the same as main's taau_upscale.comp.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the thorough review — all four addressed in 7ce5a07 (pushed to fsr1-easu-rcas, on top of the two earlier fixups):

  1. Clamp bit: taau_rcas.comp now tests (pc.valid & 1) != 0, same as main's taau_upscale.comp after feat(vk): FXAA in the gameplay TAAU stand-in (edges on cards without the network) #1115 — FXAA's bits (2, or 6 with RX_VK_TAAU_FXAA=2) can no longer turn the bound on, so no clamp against an unwritten hist on the first frame after a resize.

  2. FXAA: documented as "FSR replaces FXAA", per your either/or — the comment at the mode decision in vk_draw.cpp now says RX_VK_TAAU_FXAA belongs to the bilinear stand-in only (EASU's edge-adaptive kernel does the edge work it was added for), and the readme's switches section says the same: the two never run together, RX_VK_TAAU_MODE=bilinear gets the FXAA picture back.

  3. Fixture: tests/test_taau_env_lookup.py extended to the current shape — ViewUse::Sample + VK_FORMAT_UNDEFINED accepted by the texture_view_of spy, a view_type stub, Texture::width/height, a 10-arg typed_upscale spy that checks the forwarded source size (width >> level), and a new no-resolved-input rejection case. All seven configurations pass. On the behaviour note: I kept the return-false (network runs) when the input can't be resolved rather than falling back to bilinear — it only happens when the source texture fails to resolve, in which case the blit fallback in upscale_stub wouldn't have the size either. Happy to switch it if you'd rather.

  4. Colour space: done as you suggested — the c * c decode is gone from EASU's loads, and RCAS squares once at its output, before the history bound. EASU and RCAS now run on the sqrt-encoded input (the space FSR 1's weights were tuned for, RCAS's 1 - mx4 in [0,1]), and mid's B10G11R11 keeps more of the darks. SPV headers regenerated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, all four look good. Keeping return-false when the input can't be resolved is fine with me: as you say, the blit fallback wouldn't have the size either.

Comment thread gpu/vk/vk_draw.cpp
if (!src_w || !src_h) return false;
// (FSR 1's EASU + RCAS in the stand-in's place (shaders/taau_easu.comp, taau_rcas.comp); RX_VK_TAAU_MODE=bilinear:
// the old one-tap stand-in, for an A/B. RX_VK_TAAU_SHARPNESS=<stops> (0 the sharpest, 2 soft; default 0.2))
static const bool bilinear = getenv("RX_VK_TAAU_MODE") && !strcmp(getenv("RX_VK_TAAU_MODE"), "bilinear");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is where the decision against #1115's FXAA goes (see the review body). As written after a rebase, the FSR path would silently drop FXAA, and RX_VK_TAAU_FXAA would only do anything when RX_VK_TAAU_MODE=bilinear is set. If that's the plan, please say so in the comment here and in the readme/env docs.

Comment thread gpu/vk/vk_draw.cpp
Texture* in_tex = nullptr;
uint32_t in_level = 0;
if (in_handle) texture_view_of(in_handle, view_type(td.type), ViewUse::Sample, VK_FORMAT_UNDEFINED, &in_tex, &in_level);
if (!in_tex) return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

tests/test_taau_env_lookup.py compiles this function out of vk_draw.cpp against spies. With the extra texture_view_of(..., ViewUse::Sample, ...) call and the two new typed_upscale args, the fixture won't build, and its texture_view_of spy also rejects anything that isn't Storage. Please extend the fixture: add view_type, ViewUse::Sample, Texture::width/height, and a 10-arg spy that checks the forwarded source size.

Also a small behaviour change: if the input texture can't be resolved, the stand-in now returns false and the network runs, where before it would still do the linear upscale. That's probably fine, but you could fall back to the bilinear pipeline instead of giving up.

Comment thread gpu/vk/shaders/taau_easu.comp Outdated
vec3 ld(ivec2 q) { // the source texel, squared (linear), clamped to the image
q = clamp(q, ivec2(0), pc.src_size - 1);
const vec3 c = texelFetch(src, q, 0).rgb;
return c * c;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit / suggestion: FSR 1 expects EASU and RCAS to run on perceptually encoded input, and the input is already sqrt-encoded. If you filter on c as-is and square once at the end of RCAS (before the history clamp), the edge detection and luma weights work in the space they were tuned for. B10G11R11 also keeps more precision in the darks for mid that way. This matters for RCAS too: its 1 - mx4 term assumes [0,1], so any pixel near a highlight above 1.0 in linear gets no sharpening at all (lobe clamps to 0). That's harmless, just worth knowing.

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.

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick turnaround. All four points from last time are handled in 7ce5a07:

  • RCAS now tests (pc.valid & 1) != 0, so FXAA's bits can't turn the clamp on.
  • Making FSR the default and keeping FXAA for RX_VK_TAAU_MODE=bilinear is a fine call, and the comment and readme spell it out.
  • The test_taau_env_lookup.py fixture now matches the new shape: it has the Sample view, source size forwarding, and the new no-input case. Main's copy of that test hasn't changed since your base, so this should carry over cleanly.
  • EASU/RCAS now filter the sqrt-encoded input and square once before the bound. That looks right.

Two things still stop this from being mergeable:

  1. Two unrelated commits are on the branch now. 26b9466 ("a readback's wait flushes the held batch", vk_replay.cpp) and 8fb0290 ("graceful retired-pool eviction", vk_memory.cpp) aren't about the upscaler, and they look like they were pushed to the wrong branch. The vk_memory one changes default behaviour for everyone on Windows: retired pools used to be kept on Intel/AMD and are now given back after 30s. That keep-by-default was there because of device losses on Arc (issue 17), the Ally X and the RX 6600. The vk_replay one adds a batch flush from wait_recorded. Both may well be good changes, but they need their own PRs with their own testing. Please drop them from this one (see inline).
  2. The branch is still on an old main (about 490 commits behind) and conflicts. The fixes above are written against #1115, but #1115 isn't on the branch. Head's taau_upscale.comp and typed_upscale have no FXAA yet, so the actual merge with main's typed_upscale (FXAA's size[2] |= bits, the RX_VK_TAAU_FXAA getenv) and with #1008's edits around taau_stand_in is still to be done. Please merge or rebase on main. After that, tests/test_taau_fxaa.py needs the if (temporal && fxaa) size[2] |= ... line and the RX_VK_TAAU_FXAA getenv to stay in typed_upscale. That's fine with your design: the bilinear path keeps using them, and RCAS ignores bit 2. The new comment saying the flag "is read by nothing here" will need adjusting once that getenv sits in the same function.

On #1008 and #1386: no clash. FSR 1 is spatial, and the TemporalEngine interface in vk_temporal.cpp is for engines that use jitter, motion vectors and the actor-motion pass. So it's right for this to stay the fallback under fsr3_stand_in/temporal_stand_in and not become another engine. It doesn't use the dlss option values (3/4/5 = FSR 3.1/4/4 AA) or add any settings ids, and RX_VK_TAAU_MODE/RX_VK_TAAU_SHARPNESS don't exist on main. Since #1386 is merged and vendors ffx_fsr1.h, you could include from there someday, but that's not a blocker.

CI reports no checks on this branch.

Comment thread gpu/vk/vk_memory.cpp
// 85) the wait shortens to RX_VK_RETIRE_MIN_SECS seconds (default 2), down to RX_VK_IMPORT_LOW percent (75), and
// the free itself still waits for the GPU (dead_imports). RX_VK_KEEP_RETIRED=1 keeps them for the session; =0
// gives them back as soon as the GPU is past, both as before. The vendor split is gone: every vendor leaked by it
static const bool keep = getenv("RX_VK_KEEP_RETIRED") && getenv("RX_VK_KEEP_RETIRED")[0] != '0';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This (and the rest of 8fb0290) doesn't belong in the FSR 1 PR. It removes the Windows keep-by-default for non-NVIDIA cards, which was added after the Arc B390 / Ally X / RX 6600 device losses. A time-based give-back may be the right fix, but it needs its own PR and testing on those vendors. Please drop it from this branch.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved: this is out of the branch now and lives in #1906.

Comment thread gpu/vk/vk_replay.cpp Outdated
// set for another's flush, which queue_flush_locked's empty return would do)
{
std::lock_guard lk(dev.present_mu);
if (seq > rec_out && !pend.cbs.empty()) flush_reason = FR_READBACK, queue_flush_locked();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same here: 26b9466 (the readback-triggered batch flush) is unrelated to the upscaler. Please move it to its own PR so it can be reviewed and A/B'd on its own.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved: dropped from this branch, and it's now #1905.

Comment thread gpu/vk/vk_draw.cpp
if (!src_w || !src_h) return false;
// (FSR 1's EASU + RCAS in the stand-in's place (shaders/taau_easu.comp, taau_rcas.comp); RX_VK_TAAU_MODE=bilinear:
// the old one-tap stand-in, for an A/B. RX_VK_TAAU_SHARPNESS=<stops> (0 the sharpest, 2 soft; default 0.2).
// RX_VK_TAAU_FXAA (PR #1115) belongs to the bilinear stand-in only: the FSR path replaces it, its EASU kernel

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

After merging main, typed_upscale will contain the RX_VK_TAAU_FXAA getenv and the if (temporal && fxaa) size[2] |= ... line, and test_taau_fxaa.py requires both. So "read by nothing here" won't be accurate. Something like "only the bilinear pipeline acts on it; RCAS tests bit 0 alone" would fit.

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.

@hckrman101

Copy link
Copy Markdown
Collaborator

Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!

@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.

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for splitting the branch. The two unrelated commits are gone: the PR is now only the two EASU commits, and the readback flush and the retired-pool change are in #1905 and #1906. I checked the PR's files at b1009cc against 7ce5a07, and they're identical, so the four fixes from the earlier rounds are all still in.

The rebase isn't on the branch yet, though. Your comment says it's rebased onto v2.0.14, but fsr1-easu-rcas is still at b1009cc on top of 581e83f. That's 921 commits behind main, and GitHub reports it as conflicting. Maybe the rebased branch didn't get pushed? A trial three-way merge of the PR's files against current main gives:

  • vk_draw.cpp, 3 conflicts. (a) The typed_upscale forward declaration: main added temporal_rejected_network next to it. (b) The generic RGB9E5 fallback in upscale_stub: main now checks input_view && output_view before calling typed_upscale. Please keep that check alongside the two new size args. (c) The dispatch block at the end of typed_upscale: this is the FXAA merge from last time. Main has hist_view/hist_frame aliases, the RX_VK_TAAU_FXAA getenv and if (temporal && fxaa) size[2] |= .... The bilinear branch needs to keep those for tests/test_taau_fxaa.py. The "read by nothing here" comment also needs adjusting, as in the open inline thread.
  • readme.md, 1 conflict. It's next to the new RX_VK_WB_MAX_KB paragraph and the link to the development guide's switches section. Easy to resolve. Since main moved the test and debug switches to docs/development.md, the TAAU switches could go there too, but that's up to you.
  • New on main: tests/test_upscale_fallback.py will break. It cuts upscale_stub out of vk_draw.cpp and compiles it against an 8-arg typed_upscale spy that asserts !temporal. After the merge, the RGB9E5 fallback calls it with src->width, src->height as well, so the spy needs the two extra parameters. It could also check that they're 64x32, which is what its Texture stub already has. Please update it in the same PR, the same way you did test_taau_env_lookup.py.
  • tests/test_taau_env_lookup.py merges cleanly. Main only added RUN_ALL_ARGS = ["--cxx", "g++"].

So: the scope is fixed, and the rest is the merge with main plus that one extra fixture. Once that's pushed and python -I -B tests/run_all.py passes, I'm happy to look again. CI reports no checks on this branch.

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.

@hckrman101

Copy link
Copy Markdown
Collaborator

This now conflicts with main in tests/test_taau_env_lookup.py. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.

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.

3 participants