feat(vk): FSR 3.1 as an engine of the temporal chain, with the characters' motion #2305

Merged
unixman69 merged 16 commits from feat/fsr3-temporal-chain into main 2026-11-11 21:27:04 +00:00
unixman69 commented 2026-11-11 14:10:09 +00:00 (Migrated from github.com)

AMD FSR 3.1 makes its own inputs (vk_fsr3_inputs.cpp), and the only motion it gets is the camera's. So anything that
moves differently from the camera is given the motion of what's behind it: Link while he walks, his swinging arm, his
shield, and the people around him. DLSS and FSR 4 already get those characters' own motion on vk_temporal.cpp's chain.
This PR puts FSR 3.1 on that chain too, as a third engine, so all three temporal upscalers share the same inputs.

What changed

  • vk_fsr3.cpp/.h: kChainEngine, FSR 3.1 as a TemporalEngine (slot 2).
    • It takes the chain's colour, depth, motion (with the actor pass drawn over it), jitter and jitter phase count.
    • It builds FSR's depth constants from the unjittered projection.
    • Its picture is copied into the chain's output and squared back by dlss_out.comp as before.
    • fsr3_chosen() is "the row or RX_VK_FSR=1 picks FSR 3.1". fsr3_wanted() now means "FSR 3.1 on its own inputs",
      which is false while it's on the chain.
    • fsr3::prepare_sizes and fsr3::release let the chain size and free FSR's images.
  • vk_temporal.cpp/.h:
    • temporal_mode returns FSR3 for the row's FSR 3.1 before it falls back to the game's own; engine_of gets a third
      slot.
    • Two new TemporalEngine fields:
      • fsr_depth: the chain reverses depth for FSR's passes (built with INVERTED_DEPTH) where the projection puts the
        near plane at 0.
      • retry: a failed evaluation hands that frame to the network instead of turning the engine off for good. FSR 3.1's
        constants come from the frame's stream, which can run out for one frame.
    • TemporalFrame carries the unjittered projection and the jitter phase count.
    • The chain's output image gets TRANSFER_DST.
    • RX_VK_FSR_JITTER=0 still turns FSR 3.1's jitter off on the chain.
  • shaders/dlss_prep.comp:
    • Flag bit 3 reverses depth (1 - d).
    • A NaN in the scene colour is written as black, following fsr3_prep.comp's rule, because one NaN spreads through an
      upscaler's history.
    • The header was regenerated with glslangValidator -V --target-env vulkan1.3 --vn DLSS_PREP_COMP_SPV, the command
      test_dlss_actor.py checks.
  • RX_VK_FSR3_CHAIN=0 gives FSR 3.1 its own inputs, as before, for comparisons (docs/development.md). The readme's FSR 3.1
    section describes the new inputs, and says what still trails: things that aren't skinned, such as Navi's body.
  • Tests:
    • temporal_jitter_test.cpp covers FSR 3.1's slot and its failure gate, and a new run (fsr-jitter-off) where only
      RX_VK_FSR_JITTER=0 is set: FSR 3.1 on the chain gets no jitter, and DLSS and FSR 4 keep theirs.
    • test_upscaler_rows.py checks that the row's FSR 3.1 runs on the chain before the fallback to the game's own, that
      fsr3_wanted is off while it does, and that engine_of has its slot.
    • Both fail on main. A mutated jitter gate fails fsr-jitter-off.

Testing

  • python -I -B tests/run_all.py: 363 passed, 0 failed, 11 skipped (Linux; the skips are Windows-only, need a Vulkan 1.3
    device, or need dotnet).

  • In game: RX 9070 XT 16 GB, Mesa RADV 26.2.6, Gentoo Linux (kernel 7.3.3), KDE Plasma Wayland, windowed, 2560x1440
    output, 60 fps cap.

    • Host v2.1.0-main-local (source fc2ed0d0). Renderer: main 49f75dc8 plus this branch.
    • "Own" means the same build with RX_VK_FSR3_CHAIN=0, which is what main does.
    • Every chain run logged vk: fsr3 on the chain: 600 frames drawn ... depth reversed for FSR by the chain, with no
      evaluation failures.
    FSR 3.1 Kokiri Forest, 28 s camera turn (4 runs each) Castle Town market at noon (3 runs each)
    Own inputs (as main) 59.2 fps, 1% low 46.4 53.1 fps
    On the chain 59.3 fps, 1% low 48.1 52.5 fps

    Market runs vary by about 0.9 fps between runs, so the chain costs nothing measurable.

  • Picture: consecutive 2560x1440 shots at Kokiri's lost-woods path, standing and walking, compared on the same build.
    Edge flicker is the mean frame-to-frame change of edge pixels, 0-255, while standing.

    FSR 3.1 Edge flicker, standing 1-px outlines per MP Halos per MP
    100%, own 2.17 32 45
    100%, chain 2.20 29 50
    Quality (67%), own 3.01 12 25
    Quality (67%), chain 1.67 2 9

    Four independent reviewers of the walking sheets saw no visible difference at 100%. At 67%, they found the chain
    slightly better: smoother edges on Link's shield and tunic hem, and slightly softer shield texture. No new artefacts.
    The clearest evidence is the motion field itself (below). On main, Link isn't in it at all.

Not tested:

  • other GPUs and drivers;
  • Windows;
  • Steam Deck, where #2110 would make FSR 3.1 the default;
  • frame interpolation (RX_VK_FSR_FG=1), which keeps using vk_fsr3_inputs.cpp;
  • switching upscalers repeatedly while playing (release and remake are code-reviewed, not exercised in game);
  • HDR: the chain still clamps FSR 3.1's colour at the paper white, as main does (see below).

Screenshots (from the game's output image before the present path, 2560x1440)

The motion vectors the upscaler gets while Link walks (RX_VK_DLSS_DEBUG=motion). Left: camera motion only
(RX_VK_DLSS_ACTORS=0, which is what FSR 3.1 got on main). Right: on the chain, with Link's body, arm and shield carrying
their own motion:

motion vectors

Link walking: own inputs vs the chain, at 100% and at 67% (2x zoom crops):

walking

Merge notes (from test merges against every open PR that touches these files; textual conflicts show up in git, semantic ones don't)

  • Depth is reversed for FSR 3.1 on the chain. TemporalFrame::depth_reversed says so. Anything that reads the chain's
    depth must check it rather than assume plain depth:
    • #2118 (DLSS frame generation through NGX, now on main): done in this PR. Commit ca2e3022 passes depth_reversed to
      NGX's depthInverted, and test_upscaler_rows checks it.
    • #1944 (DLSS frame generation through Streamline) needs the same: depthInverted from depth_reversed, not a
      hard-coded false.
    • #1831/#1833: nr_pre passes s.depth on, and the NGX backend's DepthInverted should follow it too.
  • #2145 adds bool native_aa to TemporalEngine::prepare. chain_prepare in vk_fsr3.cpp needs the same
    parameter, or the build breaks. git merges the two cleanly. FSR 3.1 never has native AA.
  • #1653 (reactive mask) is on main now and merged into this branch. Commit 7ceae45c makes the mask in chain_evaluate
    the same way DLSS does on the chain, and test_reactive_mask.py checks it. Checked in game with RX_VK_REACTIVE=1: the mask is
    made, and FSR 3.1 draws all 600 frames on the chain.
  • #2243 (DLSS HDR, linear light) is on main now (in #2342's squash, a0dfd28b) and is merged into this branch.
    • dlss_prep.comp scrubs NaN first, then applies #2243's max/kSquare/min, and keeps the flags & 8 depth flip.
      The header was regenerated; glslang 16.4.0 gives the same SPIR-V words as CI's 16.6.0 for dlss_prep and dlss_out.
    • TemporalEngine is ordered memory, hdr, fsr_depth, retry: DLSS's and FSR 4's engines are positional,
      and kChainEngine is designated.
    • FSR 3.1 doesn't report hdr yet, so it keeps the LDR pair. Giving it HDR colour on #2243's mechanism is a follow-up.
  • #1831:
    • It reads fsr3_wanted() to ask whether FSR 3.1 is chosen. After this PR that question is fsr3_chosen(). Its
      fsr4 = !fsr3_wanted() && fsr4_wanted() guard and its jitter guard should use fsr3_chosen(). The merge is
      textually clean, so git won't flag it.
    • In vk_temporal.cpp, the output image's usage should become TRANSFER_DST | TRANSFER_SRC, and its proj[16] and
      this PR's projection[16] should become one field.
  • #2166 only matters with RX_VK_FSR3_CHAIN=0.
  • docs/tested-configurations.md and docs/development.md: rows next to other PRs' rows; keep both.

🤖 Generated with Claude Code

AMD FSR 3.1 makes its own inputs (`vk_fsr3_inputs.cpp`), and the only motion it gets is the camera's. So anything that moves differently from the camera is given the motion of what's behind it: Link while he walks, his swinging arm, his shield, and the people around him. DLSS and FSR 4 already get those characters' own motion on `vk_temporal.cpp`'s chain. This PR puts FSR 3.1 on that chain too, as a third engine, so all three temporal upscalers share the same inputs. **What changed** - `vk_fsr3.cpp/.h`: `kChainEngine`, FSR 3.1 as a `TemporalEngine` (slot 2). - It takes the chain's colour, depth, motion (with the actor pass drawn over it), jitter and jitter phase count. - It builds FSR's depth constants from the unjittered projection. - Its picture is copied into the chain's output and squared back by `dlss_out.comp` as before. - `fsr3_chosen()` is "the row or `RX_VK_FSR=1` picks FSR 3.1". `fsr3_wanted()` now means "FSR 3.1 on its own inputs", which is false while it's on the chain. - `fsr3::prepare_sizes` and `fsr3::release` let the chain size and free FSR's images. - `vk_temporal.cpp/.h`: - `temporal_mode` returns `FSR3` for the row's FSR 3.1 before it falls back to the game's own; `engine_of` gets a third slot. - Two new `TemporalEngine` fields: - `fsr_depth`: the chain reverses depth for FSR's passes (built with `INVERTED_DEPTH`) where the projection puts the near plane at 0. - `retry`: a failed evaluation hands that frame to the network instead of turning the engine off for good. FSR 3.1's constants come from the frame's stream, which can run out for one frame. - `TemporalFrame` carries the unjittered projection and the jitter phase count. - The chain's output image gets `TRANSFER_DST`. - `RX_VK_FSR_JITTER=0` still turns FSR 3.1's jitter off on the chain. - `shaders/dlss_prep.comp`: - Flag bit 3 reverses depth (`1 - d`). - A NaN in the scene colour is written as black, following `fsr3_prep.comp`'s rule, because one NaN spreads through an upscaler's history. - The header was regenerated with `glslangValidator -V --target-env vulkan1.3 --vn DLSS_PREP_COMP_SPV`, the command `test_dlss_actor.py` checks. - `RX_VK_FSR3_CHAIN=0` gives FSR 3.1 its own inputs, as before, for comparisons (`docs/development.md`). The readme's FSR 3.1 section describes the new inputs, and says what still trails: things that aren't skinned, such as Navi's body. - Tests: - `temporal_jitter_test.cpp` covers FSR 3.1's slot and its failure gate, and a new run (`fsr-jitter-off`) where only `RX_VK_FSR_JITTER=0` is set: FSR 3.1 on the chain gets no jitter, and DLSS and FSR 4 keep theirs. - `test_upscaler_rows.py` checks that the row's FSR 3.1 runs on the chain before the fallback to the game's own, that `fsr3_wanted` is off while it does, and that `engine_of` has its slot. - Both fail on main. A mutated jitter gate fails `fsr-jitter-off`. **Testing** - `python -I -B tests/run_all.py`: 363 passed, 0 failed, 11 skipped (Linux; the skips are Windows-only, need a Vulkan 1.3 device, or need dotnet). - In game: RX 9070 XT 16 GB, Mesa RADV 26.2.6, Gentoo Linux (kernel 7.3.3), KDE Plasma Wayland, windowed, 2560x1440 output, 60 fps cap. - Host v2.1.0-main-local (source fc2ed0d0). Renderer: main 49f75dc8 plus this branch. - "Own" means the same build with `RX_VK_FSR3_CHAIN=0`, which is what main does. - Every chain run logged `vk: fsr3 on the chain: 600 frames drawn ... depth reversed for FSR by the chain`, with no evaluation failures. | FSR 3.1 | Kokiri Forest, 28 s camera turn (4 runs each) | Castle Town market at noon (3 runs each) | |---|---|---| | Own inputs (as main) | 59.2 fps, 1% low 46.4 | 53.1 fps | | On the chain | 59.3 fps, 1% low 48.1 | 52.5 fps | Market runs vary by about 0.9 fps between runs, so the chain costs nothing measurable. - Picture: consecutive 2560x1440 shots at Kokiri's lost-woods path, standing and walking, compared on the same build. Edge flicker is the mean frame-to-frame change of edge pixels, 0-255, while standing. | FSR 3.1 | Edge flicker, standing | 1-px outlines per MP | Halos per MP | |---|---|---|---| | 100%, own | 2.17 | 32 | 45 | | 100%, chain | 2.20 | 29 | 50 | | Quality (67%), own | 3.01 | 12 | 25 | | Quality (67%), chain | 1.67 | 2 | 9 | Four independent reviewers of the walking sheets saw no visible difference at 100%. At 67%, they found the chain slightly better: smoother edges on Link's shield and tunic hem, and slightly softer shield texture. No new artefacts. The clearest evidence is the motion field itself (below). On main, Link isn't in it at all. **Not tested:** - other GPUs and drivers; - Windows; - Steam Deck, where #2110 would make FSR 3.1 the default; - frame interpolation (`RX_VK_FSR_FG=1`), which keeps using `vk_fsr3_inputs.cpp`; - switching upscalers repeatedly while playing (release and remake are code-reviewed, not exercised in game); - HDR: the chain still clamps FSR 3.1's colour at the paper white, as main does (see below). **Screenshots** (from the game's output image before the present path, 2560x1440) The motion vectors the upscaler gets while Link walks (`RX_VK_DLSS_DEBUG=motion`). Left: camera motion only (`RX_VK_DLSS_ACTORS=0`, which is what FSR 3.1 got on main). Right: on the chain, with Link's body, arm and shield carrying their own motion: ![motion vectors](https://raw.githubusercontent.com/unixman69/Hookshot/pr-screenshots/fsr3-chain/motion-vectors.png) Link walking: own inputs vs the chain, at 100% and at 67% (2x zoom crops): ![walking](https://raw.githubusercontent.com/unixman69/Hookshot/pr-screenshots/fsr3-chain/link-walking-own-vs-chain.png) **Merge notes** (from test merges against every open PR that touches these files; textual conflicts show up in git, semantic ones don't) - **Depth is reversed for FSR 3.1 on the chain.** `TemporalFrame::depth_reversed` says so. Anything that reads the chain's depth must check it rather than assume plain depth: - #2118 (DLSS frame generation through NGX, now on main): done in this PR. Commit ca2e3022 passes `depth_reversed` to NGX's `depthInverted`, and `test_upscaler_rows` checks it. - #1944 (DLSS frame generation through Streamline) needs the same: `depthInverted` from `depth_reversed`, not a hard-coded false. - #1831/#1833: `nr_pre` passes `s.depth` on, and the NGX backend's `DepthInverted` should follow it too. - **#2145** adds `bool native_aa` to `TemporalEngine::prepare`. `chain_prepare` in `vk_fsr3.cpp` needs the same parameter, or the build breaks. git merges the two cleanly. FSR 3.1 never has native AA. - **#1653** (reactive mask) is on main now and merged into this branch. Commit 7ceae45c makes the mask in `chain_evaluate` the same way DLSS does on the chain, and `test_reactive_mask.py` checks it. Checked in game with `RX_VK_REACTIVE=1`: the mask is made, and FSR 3.1 draws all 600 frames on the chain. - **#2243** (DLSS HDR, linear light) is on main now (in #2342's squash, a0dfd28b) and is merged into this branch. - `dlss_prep.comp` scrubs NaN first, then applies #2243's `max`/`kSquare`/`min`, and keeps the `flags & 8` depth flip. The header was regenerated; glslang 16.4.0 gives the same SPIR-V words as CI's 16.6.0 for `dlss_prep` and `dlss_out`. - `TemporalEngine` is ordered `memory`, `hdr`, `fsr_depth`, `retry`: DLSS's and FSR 4's engines are positional, and `kChainEngine` is designated. - FSR 3.1 doesn't report `hdr` yet, so it keeps the LDR pair. Giving it HDR colour on #2243's mechanism is a follow-up. - **#1831**: - It reads `fsr3_wanted()` to ask whether FSR 3.1 is chosen. After this PR that question is `fsr3_chosen()`. Its `fsr4 = !fsr3_wanted() && fsr4_wanted()` guard and its jitter guard should use `fsr3_chosen()`. The merge is textually clean, so git won't flag it. - In `vk_temporal.cpp`, the output image's usage should become `TRANSFER_DST | TRANSFER_SRC`, and its `proj[16]` and this PR's `projection[16]` should become one field. - **#2166** only matters with `RX_VK_FSR3_CHAIN=0`. - `docs/tested-configurations.md` and `docs/development.md`: rows next to other PRs' rows; keep both. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 (Migrated from github.com) reviewed 2026-11-11 18:27:28 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, this is a clear improvement: FSR 3.1 finally gets the skinned draws' motion, and the motion-vector screenshot makes the case well. I went through the inputs and they line up with what FSR 3.1 got on its own path:

  • Motion: the chain's motion is in render pixels toward the frame before with the jitter taken off, the same convention as vk_fsr3_inputs.cpp, and chain_evaluate leaves motion_scale at 1, so motion_vector_scale is 1/render size as before. Low-res motion, no display-res mixup (FSR 3.1 interpolation's #1943 question doesn't apply here, since FG keeps its own inputs).
  • Jitter: same jitter_to_pixels, and the phase count comes from the same dlss_jitter_phases the chain publishes. RX_VK_FSR_JITTER=0 still works, the jitter gate has its own slot, and vk_fsr3_inputs' own jitter stays off while FSR 3.1 is on the chain (it is gated on fsr3_wanted), so there's no double jitter.
  • Depth: the flip uses camera_from_projection on the unjittered projection, the same test the own path makes. Motion is still computed from the unflipped d, actor_motion reads the game's depth view rather than s.depth, and depth_reversed reaches NGX frame generation.
  • History: cuts and gaps reset through the chain's existing reprojection/gap logic. A change of engine releases FSR's images, and prepare_sizes then returns 2, so the next frame resets. The retry path clears last_frame, so the frame after a failed evaluation starts a new history.
  • #2166 still works on the chain: in temporal_rejected_network, fsr3_upscale now returns false and temporal_wanted() is true for FSR3, so left-out networks go through temporal_upscale. (Nit: that path's log line will then say "the temporal upscaler" rather than FSR 3.1.)
  • #1654's LOD bias: upscale_lod_bias falls through to temporal_mode(), which now returns FSR3, so the bias is unchanged. Same for the menu's upscaler().
  • DLSS and FSR 4: unchanged apart from the NaN scrub in dlss_prep.comp (harmless, arguably a fix) and bit 3, which is never set for them.
  • FSR 3.1 interpolation (RX_VK_FSR_FG=1): it keeps vk_fsr3_inputs as it already does while DLSS upscales, so this is the same pairing as DLSS + FSR FG. Fine as untested, but a quick run would be nice.

What blocks merging right now:

  1. It conflicts with main. GitHub reports the PR as not mergeable. #2166 is already in your base, so that part is fine, but #2342 (FSR 4 native HDR, merged since) touches the same lines: dlss_prep.comp's colour store (max/kSquare/kColorMax) and its SPIR-V header, the pc.flags line and the prep/out pipeline selection in temporal_upscale, TemporalEngine (hdr after memory), TemporalFrame's comment, and the docs rows. Your merge notes for #2243 apply to #2342 almost as written:
    • In the shader, scrub the NaN before the max(e, 0). GLSL's max with a NaN operand is undefined.
    • Keep kChainEngine's designated order valid (.hdr sits before .fsr_depth).
    • Regenerate dlss_prep_comp_spv.h.
    • Check that bit 3 reaches prep_hdr too.
  2. CI hasn't run. Only the title check ran, because there is no merge ref while the PR conflicts. Please rebase so the build and tests/run_all.py run on Windows and Linux.

Smaller points:

  • #1653 (reactive mask, still open) attaches its mask in fsr3_upscale, which is now only the RX_VK_FSR3_CHAIN=0 path. Whichever of the two lands second needs to carry the mask through TemporalFrame into chain_evaluate, or FSR 3.1 loses it by default. Good that you flagged this in the merge notes.
  • Since this becomes the default for FSR 3.1, which is the upscaler for GPUs without DLSS/FSR 4 (and the Steam Deck default with #2110), one frame-time number on a weaker GPU or iGPU would help. The chain adds the actor-motion pass and a display-size copy. That costs nothing measurable on a 9070 XT, but low-end hardware is where FSR 3.1 is used.

The screenshots and numbers are there, thanks. The 67% flicker and outline numbers are convincing. Once it's rebased and CI is green I'd be happy to approve.

Thanks, this is a clear improvement: FSR 3.1 finally gets the skinned draws' motion, and the motion-vector screenshot makes the case well. I went through the inputs and they line up with what FSR 3.1 got on its own path: - Motion: the chain's motion is in render pixels toward the frame before with the jitter taken off, the same convention as `vk_fsr3_inputs.cpp`, and `chain_evaluate` leaves `motion_scale` at 1, so `motion_vector_scale` is 1/render size as before. Low-res motion, no display-res mixup (FSR 3.1 interpolation's #1943 question doesn't apply here, since FG keeps its own inputs). - Jitter: same `jitter_to_pixels`, and the phase count comes from the same `dlss_jitter_phases` the chain publishes. `RX_VK_FSR_JITTER=0` still works, the jitter gate has its own slot, and `vk_fsr3_inputs`' own jitter stays off while FSR 3.1 is on the chain (it is gated on `fsr3_wanted`), so there's no double jitter. - Depth: the flip uses `camera_from_projection` on the unjittered projection, the same test the own path makes. Motion is still computed from the unflipped `d`, `actor_motion` reads the game's depth view rather than `s.depth`, and `depth_reversed` reaches NGX frame generation. - History: cuts and gaps reset through the chain's existing reprojection/gap logic. A change of engine releases FSR's images, and `prepare_sizes` then returns 2, so the next frame resets. The `retry` path clears `last_frame`, so the frame after a failed evaluation starts a new history. - #2166 still works on the chain: in `temporal_rejected_network`, `fsr3_upscale` now returns false and `temporal_wanted()` is true for FSR3, so left-out networks go through `temporal_upscale`. (Nit: that path's log line will then say "the temporal upscaler" rather than FSR 3.1.) - #1654's LOD bias: `upscale_lod_bias` falls through to `temporal_mode()`, which now returns FSR3, so the bias is unchanged. Same for the menu's `upscaler()`. - DLSS and FSR 4: unchanged apart from the NaN scrub in `dlss_prep.comp` (harmless, arguably a fix) and bit 3, which is never set for them. - FSR 3.1 interpolation (`RX_VK_FSR_FG=1`): it keeps `vk_fsr3_inputs` as it already does while DLSS upscales, so this is the same pairing as DLSS + FSR FG. Fine as untested, but a quick run would be nice. What blocks merging right now: 1. **It conflicts with main.** GitHub reports the PR as not mergeable. #2166 is already in your base, so that part is fine, but #2342 (FSR 4 native HDR, merged since) touches the same lines: `dlss_prep.comp`'s colour store (`max`/`kSquare`/`kColorMax`) and its SPIR-V header, the `pc.flags` line and the prep/out pipeline selection in `temporal_upscale`, `TemporalEngine` (`hdr` after `memory`), `TemporalFrame`'s comment, and the docs rows. Your merge notes for #2243 apply to #2342 almost as written: - In the shader, scrub the NaN *before* the `max(e, 0)`. GLSL's `max` with a NaN operand is undefined. - Keep `kChainEngine`'s designated order valid (`.hdr` sits before `.fsr_depth`). - Regenerate `dlss_prep_comp_spv.h`. - Check that bit 3 reaches `prep_hdr` too. 2. **CI hasn't run.** Only the title check ran, because there is no merge ref while the PR conflicts. Please rebase so the build and `tests/run_all.py` run on Windows and Linux. Smaller points: - #1653 (reactive mask, still open) attaches its mask in `fsr3_upscale`, which is now only the `RX_VK_FSR3_CHAIN=0` path. Whichever of the two lands second needs to carry the mask through `TemporalFrame` into `chain_evaluate`, or FSR 3.1 loses it by default. Good that you flagged this in the merge notes. - Since this becomes the default for FSR 3.1, which is the upscaler for GPUs without DLSS/FSR 4 (and the Steam Deck default with #2110), one frame-time number on a weaker GPU or iGPU would help. The chain adds the actor-motion pass and a display-size copy. That costs nothing measurable on a 9070 XT, but low-end hardware is where FSR 3.1 is used. The screenshots and numbers are there, thanks. The 67% flicker and outline numbers are convincing. Once it's rebased and CI is green I'd be happy to approve.
hckrman101 (Migrated from github.com) commented 2026-11-11 18:27:28 +00:00

Nit: the chain's status line will now show FSR 3.1 using no VRAM of its own, though its internal images (history, luma pyramid, the RGBA16F output) are tens of MB at 1440p. If fsr3:: can sum the image sizes it made, it's cheap to report them here.

Nit: the chain's status line will now show FSR 3.1 using no VRAM of its own, though its internal images (history, luma pyramid, the RGBA16F output) are tens of MB at 1440p. If `fsr3::` can sum the image sizes it made, it's cheap to report them here.
unixman69 (Migrated from github.com) reviewed 2026-11-11 18:35:56 +00:00
unixman69 (Migrated from github.com) commented 2026-11-11 18:35:56 +00:00

Done in 514c7ed5c: fsr3::memory() adds up the internal images and kChainEngine's memory reports it, so the status line now shows FSR 3.1's own memory (217 MB at 2560x1440 native here). The #2166 log line for a left-out network now says "FSR 3.1 (on the temporal chain)" too.

🤖 Addressed by Claude Code

Done in 514c7ed5c: `fsr3::memory()` adds up the internal images and `kChainEngine`'s `memory` reports it, so the status line now shows FSR 3.1's own memory (217 MB at 2560x1440 native here). The #2166 log line for a left-out network now says "FSR 3.1 (on the temporal chain)" too. _🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
unixman69 commented 2026-11-11 18:45:07 +00:00 (Migrated from github.com)

Thanks for the thorough read. Where each point stands:

  • Conflicts: main is merged in, #2342 included, and the PR is mergeable again. I used merges rather than a rebase so the reviewed commits keep their hashes.
    • dlss_prep.comp scrubs NaN before the max, then applies kSquare/kColorMax, and keeps the flags & 8 flip.
    • Bit 3 reaches both prep pipelines: they share the shader and the push constants.
    • TemporalEngine is ordered memory, hdr, fsr_depth, retry, and kChainEngine stays designated.
    • The header was regenerated. glslang 16.4.0 gives the same SPIR-V words as CI's 16.6.0 for dlss_prep and dlss_out; I checked against main's headers.
  • #2118's frame generation was merged into this branch on the way. Its depthInverted now follows depth_reversed (ca2e3022), and a test covers it.
  • Status line memory and the #2166 log label: both done (514c7ed5). The status line now shows FSR 3.1's own memory, 217 MB at 2560x1440 native.
  • FSR 3.1 interpolation: I ran RX_VK_FSR_FG=1 with frame generation at 2x and FSR 3.1 on the chain. It made every interpolated frame in gameplay (600 of 600 per status window); the only frames without its inputs were before gameplay. No device loss.
  • CI has run on the current head (879dd6b1). Everything passes except "Python source checks (windows-2022, part 1/2)", and its one failure is tests/test_native_astc_texture_count.py: #2288's fixture uses scanf, which the Windows CRT marks deprecated under -Werror. Main fails that check the same way, and #2397 fixes it, so it should go green once #2397 lands and I merge main again. Locally tests/run_all.py passes 391 of 391 on Linux.
  • Weaker GPU: I only have the RX 9070 XT, so I can't give a low-end frame time. On it, the chain's extra work (actor-motion pass and display-size copy) doesn't show in the frame rate, and the actor pass costs about 0.03 ms of CPU per frame. A Steam Deck or iGPU number from someone who has one would be welcome. RX_VK_FSR3_CHAIN=0 gives a direct comparison.
  • #1653: agreed. Whichever lands second carries the mask into chain_evaluate.
Thanks for the thorough read. Where each point stands: - **Conflicts:** main is merged in, #2342 included, and the PR is mergeable again. I used merges rather than a rebase so the reviewed commits keep their hashes. - `dlss_prep.comp` scrubs NaN before the `max`, then applies `kSquare`/`kColorMax`, and keeps the `flags & 8` flip. - Bit 3 reaches both prep pipelines: they share the shader and the push constants. - `TemporalEngine` is ordered `memory`, `hdr`, `fsr_depth`, `retry`, and `kChainEngine` stays designated. - The header was regenerated. glslang 16.4.0 gives the same SPIR-V words as CI's 16.6.0 for `dlss_prep` and `dlss_out`; I checked against main's headers. - **#2118's frame generation** was merged into this branch on the way. Its `depthInverted` now follows `depth_reversed` (ca2e3022), and a test covers it. - **Status line memory and the #2166 log label:** both done (514c7ed5). The status line now shows FSR 3.1's own memory, 217 MB at 2560x1440 native. - **FSR 3.1 interpolation:** I ran `RX_VK_FSR_FG=1` with frame generation at 2x and FSR 3.1 on the chain. It made every interpolated frame in gameplay (600 of 600 per status window); the only frames without its inputs were before gameplay. No device loss. - **CI** has run on the current head (879dd6b1). Everything passes except "Python source checks (windows-2022, part 1/2)", and its one failure is `tests/test_native_astc_texture_count.py`: #2288's fixture uses `scanf`, which the Windows CRT marks deprecated under `-Werror`. Main fails that check the same way, and #2397 fixes it, so it should go green once #2397 lands and I merge main again. Locally `tests/run_all.py` passes 391 of 391 on Linux. - **Weaker GPU:** I only have the RX 9070 XT, so I can't give a low-end frame time. On it, the chain's extra work (actor-motion pass and display-size copy) doesn't show in the frame rate, and the actor pass costs about 0.03 ms of CPU per frame. A Steam Deck or iGPU number from someone who has one would be welcome. `RX_VK_FSR3_CHAIN=0` gives a direct comparison. - **#1653:** agreed. Whichever lands second carries the mask into `chain_evaluate`.
Joey-Magnus (Migrated from github.com) approved these changes 2026-11-11 21:22:57 +00:00
Joey-Magnus (Migrated from github.com) left a comment

Every earlier blocker is handled: #2342 merged in, NaN scrubbed before max, kChainEngine order valid, SPIR-V header regenerated, bit 3 reaching both prep pipelines, and #1653's reactive mask carried into chain_evaluate. The retry path drops one frame, and projection failures are filtered before evaluate, so it doesn't spin. The 11 temporal/FSR/DLSS checks pass locally.

If you have one, a Steam Deck or iGPU frame-time number would help, since FSR 3.1 is the low-end upscaler and #2110 would make it the default.

Every earlier blocker is handled: #2342 merged in, NaN scrubbed before `max`, `kChainEngine` order valid, SPIR-V header regenerated, bit 3 reaching both prep pipelines, and #1653's reactive mask carried into `chain_evaluate`. The `retry` path drops one frame, and projection failures are filtered before `evaluate`, so it doesn't spin. The 11 temporal/FSR/DLSS checks pass locally. If you have one, a Steam Deck or iGPU frame-time number would help, since FSR 3.1 is the low-end upscaler and #2110 would make it the default.
Commenting is not possible because the repository is archived.
No description provided.