Skip to content

Add opt-in FSR 4 Native AA on Windows Vulkan - #577

Closed
oscarrc91 wants to merge 6 commits into
NotRexed:mainfrom
oscarrc91:feat/fsr4-native-aa
Closed

oscarrc91 wants to merge 6 commits into
NotRexed:mainfrom
oscarrc91:feat/fsr4-native-aa

Conversation

@oscarrc91

@oscarrc91 oscarrc91 commented Nov 8, 2026 •

Copy link
Copy Markdown

Kokiri Forest's character and foliage edges show pronounced aliasing and shimmer on my RX 9070 XT. This adds an experimental, opt-in FSR 4 Native AA resolve before the HUD, using the scene's existing dimensions. The default renderer and quality settings stay unchanged. Related requests: #305, #67 and #19.

Dependencies for the verified 1440p setup: the fixed-scale refresh from #329 (HOS_FIX_RES=1) and the base-scale option from #578 (HOS_SCENE_NATIVE=1), with fixed Resolution 100%. These are separate proposals; their code is not copied into this PR. Native AA requires matching color, depth and output dimensions. Without #329, a comparison run still rendered 1920x1080 into 2560x1440 and correctly did not initialize FSR. This branch alone is not a verified standalone configuration; please account for the dependencies before merging.

Scope

  • RX_VK_FSR_NATIVE_AA=1 enables the Windows Vulkan path with single-worker replay. RX_VK_FSR_SHARPNESS=0..1 optionally enables SDK sharpening; the default is 0.
  • Read the submitted camera through ub_read during ordered replay, match full-size D32 depth, apply viewport jitter and replace the identified final native AA compute resolve before the HUD.
  • Load the official AMD Vulkan runtime only when requested and require an actual FSR 4.x provider. Missing or unsupported runtimes retain the original resolve. Include five unmodified MIT API headers from FidelityFX SDK 2.4.0 with provenance, but no runtime DLLs.
  • Include game-free camera/option tests, CI wiring, authored compute shaders and setup/review notes in docs/fsr-native-aa.md. With the new Windows launcher layout, optional AMD DLLs belong beside the actual game and renderer in bin/.

This does not add DLSS, frame generation, internal-resolution changes, texture MIP bias, forced anisotropy, FXAA or personal cache/synchronization defaults.

Validation

  • Gameplay verified on 2026-11-08: isolated integration of this PR, Add an opt-in native scene base scale #578 and the existing Fix stale fixed-resolution scene scale (experimental opt-in) #329 commit on upstream 3f5b0aa (source fingerprint 9882ef6b). Windows, RX 9070 XT, driver 32.0.32015.7029, Ryzen 7 7800X3D; Kokiri Forest near Saria, fixed Resolution 100%, sharpening 0.4, frame generation off.
  • Logs identify FSR 4.1.2 at 2560x1440, with at least 2,520 successful dispatches. Resolve guards require matching color/depth/output sizes; the native network report also shows 2560x1440 input and output. No FSR SDK error, dispatch failure or device-lost entry appeared in the captured log. The player approved the image and stability.
  • Nineteen gameplay presentation samples had a median of 52.2 FPS (47.4-54.8). This is not a controlled benchmark or an FPS-gain claim.
  • Camera math and 26 option checks pass. Dynamic-state, draw/descriptor caching, uniform-read, pass-hazard, dispatch-clock and frame-generation regressions pass. Both authored shaders validate for Vulkan 1.2; regenerated SPIR-V matches the embedded headers, and all five API headers match AMD's pinned commit byte-for-byte.
  • Updated to upstream ddba87c: the isolated Windows Vulkan renderer compiles and links; camera/options, dynamic-state, draw/binding, pass-hazard, frame-generation, upscale-fallback, 3D-view and present-trace checks pass. This later upstream merge has build/regression coverage, not a new gameplay run.

The #329 author/commit is retained in private integration history. Neither public contribution includes its implementation. GitHub Actions for the fork contribution awaits maintainer approval; local results do not imply upstream CI has passed.

Known limits and review targets

Motion vectors are camera-only: independently moving characters, foliage and particles can ghost. There are no reactive or transparency/composition masks. The first context size is retained for the process lifetime; other sizes fall back. Intermediate native AA compute passes still execute. Camera/resolve matching is specific to the supported game build, and gameplay coverage is limited to the starting area and one GPU/driver.

Wider area/menu/cutscene tests, moving-object/disocclusion checks, resize handling and other hardware need validation. Lower resolution percentages are not validated, and #329 documents unresolved issues in those modes. The feature remains off by default. Feedback on this integration point is welcome before expanding it or adding UI/distribution integration.

AI assistance: OpenAI Codex; recorded in commit trailers. No game code, generated game shaders, saves, caches or runtime binaries are included.

oscarrc91 and others added 2 commits November 8, 2026 13:34
Use submitted camera/depth data and a pre-HUD resolve. Keep the SDK runtime optional and the default path unchanged; document camera-only motion and fixed-size context limitations.

Assisted-by: OpenAI Codex

Co-Authored-By: Codex <noreply@openai.com>
Assisted-by: OpenAI Codex

Co-Authored-By: Codex <noreply@openai.com>
oscarrc91 and others added 2 commits November 8, 2026 13:46
Assisted-by: OpenAI Codex
Co-Authored-By: Codex <noreply@openai.com>
Record that the isolated current-main run did not initialize Native AA. Keep PR 329 and the native base-scale contribution separate and state the tested integration requirements.

Assisted-by: OpenAI Codex

Co-Authored-By: Codex <noreply@openai.com>
@oscarrc91
oscarrc91 marked this pull request as ready for review November 8, 2026 13:17
oscarrc91 and others added 2 commits November 8, 2026 16:11
Assisted-by: OpenAI Codex

Co-Authored-By: Codex <noreply@openai.com>
Record the successful isolated gameplay run with the separate resolution prerequisites and clarify runtime placement for the new Windows launcher layout.

Assisted-by: OpenAI Codex

Co-Authored-By: Codex <noreply@openai.com>
@Kirkidoo

Kirkidoo commented Nov 8, 2026

Copy link
Copy Markdown

Tested on an RX 9070 XT, Windows 11. I used v2.0.2 with #329, #578, #593 and this PR, at fixed Resolution 100%, with HOS_FIX_RES=1, HOS_SCENE_NATIVE=1, RX_VK_FSR_NATIVE_AA=1, RX_VK_FSR_SHARPNESS=0.4 and RX_VK_TAAU_KEYS=-.

FSR 4 starts correctly. The log shows FSR 4.1.2 context ready, Native AA 2560x1440, and the dispatches run with no failures.

Problems I see:

Jumping into water causes lightning-like flashes at the edges of the screen.
Characters such as Link sometimes turn extremely bright for a moment.
Both go away with RX_VK_FSR_NATIVE_AA=0 and the same build and settings.

This may be the camera-only motion vectors you mention in the docs. Happy to test a new version.

Clip (FSR 4 on, jumping into water in Kokiri Forest):
https://github.com/user-attachments/assets/64cc451c-b2eb-4bed-aefd-d30b3ee94887

notquiteog pushed a commit to notquiteog/Hookshot that referenced this pull request Nov 8, 2026
…scaler

A warmed run reached gameplay and the per-network log answered the question
this series was built on:

    vk: network fd029a317a9a4b94: dispatch 32x36x1, 1 textures 1 images:
        0:1536x864; no upscaler inputs

One sampled texture, one written image, nothing else. No motion, no depth. All
eleven networks in the game are that shape: at most two sampled textures, and
never a motion or depth target among them.

So OoT's upscaler is a single-image neural upscaler, not a motion-vector
temporal one, and docs/upscaling.md's premise -- that a vendor upscaler takes
that dispatch's place -- was wrong. FSR 3.1 and DLSS Super Resolution are
temporal. They need motion and depth, and this dispatch is never handed either,
so temporal_possible() refuses at it and will keep refusing however a backend
is wired. The doc now says so above the section it contradicts.

The gate doing exactly that is the one good outcome: it refused rather than
handing FSR whichever texture happened to be bound, which is the failure the
descriptor-order commit was about.

What the run does confirm is that the opportunity is real. The input is
1536x864 against a 2048x1152 scene, so the game already renders the 3D at 75%
and upscales it. The inputs for a temporal upscaler have to be assembled
instead of taken: depth exists on the scene passes and has to be carried to the
resolve, and motion exists nowhere as a target and has to be derived from the
camera, which ghosts on anything moving by itself. That is NotRexed#577's integration
point, and this is the argument for it.

Also in here, since they were written before the run: FSR is wired to the seam
behind RX_VK_FSR=1 (it cannot fire, by the above, but the path is complete and
compiles), and the camera is no longer a precondition. The reference
implementation documents that device_to_view_depth only scales FSR's
disocclusion thresholds and uses a typical near plane and field of view, so
refusing without the real camera was stricter than the evidence warrants.

Builds clean with RX_VK_ONLY=1, 0 warnings.

Assisted-by: Claude Opus 5
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
github-actions Bot pushed a commit to notquiteog/Hookshot that referenced this pull request Nov 9, 2026
## [2.1.0](v2.0.4...v2.1.0) (2026-11-09)

### Features

* a timed span per FSR pass, so the cost has a number per pass ([1d7f262](1d7f262))
* census the render target sets, to settle whether a G-buffer exists ([8f2fda3](8f2fda3))
* compile AMD's FSR 3.1 upscaler passes from the FidelityFX SDK ([37afa2e](37afa2e)), closes [NotRexed#577](https://github.com/notquiteog/Hookshot/issues/577)
* compose the scene's view projection, and transpose it for the shader ([df82d90](df82d90))
* count FSR evaluations, because nine passes a call is cheap or ruinous ([85a3ea2](85a3ea2))
* create FSR 3.1's pipelines and internal images ([6b51433](6b51433))
* derive camera motion from the scene's depth, and keep that depth ([29c7cbe](29c7cbe))
* DLSS at the seam, and a stage switch for the FSR cost ([6aa9f48](6aa9f48))
* DLSS Super Resolution through NGX, behind a build-time SDK gate ([aff6abb](aff6abb))
* frame generation asks for its own extensions and the whole input set ([ad291d1](ad291d1))
* FSR 3 frame interpolation, and a jitter for the upscalers to undo ([384c41b](384c41b))
* give FSR the assembled depth and camera motion ([481b957](481b957))
* HOS_DISPLAY_VRR, which is what actually holds the frame rate at 60 ([c65c68e](c65c68e))
* HOS_FPS_FORCE, because raising the limiter alone does not hold ([2008ac7](2008ac7))
* HOS_VSYNC_HZ, the hardcoded 60 that actually paces the game ([5eb9f8b](5eb9f8b))
* link NGX properly, frame generation at the seam, AMD's interpolation sources ([43e3360](43e3360))
* name each network binding's role in the upscaler input contract ([d9566a1](d9566a1))
* publish Ocarina's camera, and make a backend reachable without the stand-in ([8bd7c8d](8bd7c8d))
* record FSR 3.1's nine dispatches ([8769c80](8769c80))
* route the game's upscale dispatch through one backend seam ([1a036c8](1a036c8))
* RX_VK_VP_HUNT, to find the view projection from a value only it holds ([3c02213](3c02213))
* the hunt found the projection, so widen it to find the view ([2946a32](2946a32))
* the two backends have rows in the Insert menu ([cf8b3b1](cf8b3b1))
* the vsync event follows the display, and 0 turns it off ([a67e471](a67e471))

### Bug Fixes

* a fork releases its own work, not upstream's ([47dd097](47dd097))
* clear the SPD counter between the pyramid passes (did not fix the cost) ([66e8c3a](66e8c3a))
* HOS_FPS_FORCE must not touch the nominal rate: it is slow motion on screen ([8681f2e](8681f2e))
* publish the camera from the inlined projection copies too ([c630d59](c630d59))
* read the upscaler contract in descriptor order, not from draw_sampled ([492bcd0](492bcd0))
* scan for the view projection once a target set, not once a draw ([12d4d83](12d4d83))
* snapshot the projection's fields, and name what caps the frame rate ([3790556](3790556))
* the conventionalcommits preset has to match the changelog writer ([ca6595f](ca6595f))
* the DLSS contract test links again ([86b6988](86b6988))
* the game's network takes one texture, so it is not a temporal upscaler ([853cfe3](853cfe3))
* the host camera symbols must be weak, or the renderer does not load at all ([70ecdf4](70ecdf4))
* the release's commit-analyzer preset is a package, and it was not declared ([3ff9fbd](3ff9fbd))
* the renderer would not load, and the build now says so ([c871a3c](c871a3c))
* the shader check compared glslang's version, and ran where glslang is not ([f48483c](f48483c))
* the view projection scan ran per draw, which stuttered hard ([2d9b4e8](2d9b4e8))
@NotRexed

NotRexed commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Closing to keep the queue clear — please rebase on current main and reopen; thanks!

@NotRexed NotRexed closed this Nov 9, 2026
github-actions Bot pushed a commit to notquiteog/Hookshot that referenced this pull request Nov 9, 2026
## [2.0.12](v2.0.11...v2.0.12) (2026-11-09)

### Features

* a timed span per FSR pass, so the cost has a number per pass ([1d7f262](1d7f262))
* add a Quit option to the gameplay menu ([0b02149](0b02149))
* **audio:** Audio output in the Insert menu: Auto, Stereo or 5.1 ([4856258](4856258)), closes [NotRexed#1182](https://github.com/notquiteog/Hookshot/issues/1182)
* census the render target sets, to settle whether a G-buffer exists ([8f2fda3](8f2fda3))
* compile AMD's FSR 3.1 upscaler passes from the FidelityFX SDK ([37afa2e](37afa2e)), closes [NotRexed#577](https://github.com/notquiteog/Hookshot/issues/577)
* compose the scene's view projection, and transpose it for the shader ([df82d90](df82d90))
* count FSR evaluations, because nine passes a call is cheap or ruinous ([85a3ea2](85a3ea2))
* create FSR 3.1's pipelines and internal images ([6b51433](6b51433))
* derive camera motion from the scene's depth, and keep that depth ([29c7cbe](29c7cbe))
* DLSS at the seam, and a stage switch for the FSR cost ([6aa9f48](6aa9f48))
* DLSS Super Resolution through NGX, behind a build-time SDK gate ([aff6abb](aff6abb))
* frame generation asks for its own extensions and the whole input set ([ad291d1](ad291d1))
* FSR 3 frame interpolation, and a jitter for the upscalers to undo ([384c41b](384c41b))
* give FSR the assembled depth and camera motion ([481b957](481b957))
* HOS_DISPLAY_VRR, which is what actually holds the frame rate at 60 ([c65c68e](c65c68e))
* HOS_FPS_FORCE, because raising the limiter alone does not hold ([2008ac7](2008ac7))
* HOS_VSYNC_HZ, the hardcoded 60 that actually paces the game ([5eb9f8b](5eb9f8b))
* link NGX properly, frame generation at the seam, AMD's interpolation sources ([43e3360](43e3360))
* Monitor's shape for screens narrower than 16:9 (4:3, 16:10, 5:4) ([b44c9cd](b44c9cd))
* name each network binding's role in the upscaler input contract ([d9566a1](d9566a1))
* publish Ocarina's camera, and make a backend reachable without the stand-in ([8bd7c8d](8bd7c8d))
* record FSR 3.1's nine dispatches ([8769c80](8769c80))
* route the game's upscale dispatch through one backend seam ([1a036c8](1a036c8))
* RX_VK_VP_HUNT, to find the view projection from a value only it holds ([3c02213](3c02213))
* the hunt found the projection, so widen it to find the view ([2946a32](2946a32))
* the parallel lock says which site took it, and the answer is two sites ([b8543c9](b8543c9))
* the two backends have rows in the Insert menu ([cf8b3b1](cf8b3b1))
* the two lock sites say why they had to take it ([614f552](614f552))
* the upscaler and frame generation follow the menu while you play ([61d7d4a](61d7d4a))
* the upscalers and frame generation are the native settings, not new rows ([a0ce7de](a0ce7de))
* the vsync event follows the display, and 0 turns it off ([a67e471](a67e471))
* **vk:** AMD FSR 4 upscaling and frame generation through AMD's runtime ([65003cc](65003cc))
* **vk:** choose the frame generation method in the Insert menu ([c4f7d84](c4f7d84))
* **vk:** DLSS 5 neural rendering through NGX directly, beside Streamline ([7784399](7784399))
* **vk:** DLSS 5 neural rendering, with the model's own settings in the menu ([2851bc6](2851bc6))
* **vk:** frame generation Auto: a frame for each of the display's refreshes ([a5a7440](a5a7440))
* **vk:** say why DLSS 5 neural rendering is refused, and the RTX 20-40 route ([60f1d2e](60f1d2e))
* **vk:** time a capture's GPU wait for its late pages apart in the log ([e3b622f](e3b622f))

### Bug Fixes

* a fork releases its own work, not upstream's ([47dd097](47dd097))
* a Windows build could ask for DLSS and then fail to link ([d72441a](d72441a))
* **audio:** 5.1 surround when the default output has it ([NotRexed#1124](https://github.com/notquiteog/Hookshot/issues/1124)) ([b0abeca](b0abeca))
* **audio:** a finished wave buffer reaches the game at the renderer update, not mid-pass ([28e4a11](28e4a11))
* **build:** a Windows build linked the ARM64 NGX library, not the x64 one ([70b7d25](70b7d25))
* **builder:** copies from a read-only install stay writable ([70ffa5e](70ffa5e))
* **builder:** stop when generated source cleanup fails ([d254e6f](d254e6f))
* **builder:** the window icon is copied writable too ([4b2d871](4b2d871))
* **build:** NGX asks for Microsoft's C++ runtime, which llvm-mingw has not ([114e917](114e917))
* **build:** the feature announcement swallowed the whole Vulkan build ([1004f37](1004f37))
* **build:** the twelve MSVC runtime symbols NGX asks of an llvm-mingw link ([d00159a](d00159a))
* clear the SPD counter between the pyramid passes (did not fix the cost) ([66e8c3a](66e8c3a))
* contain pipeline cache save allocation failures ([4518a20](4518a20)), closes [NotRexed#1022](https://github.com/notquiteog/Hookshot/issues/1022)
* HOS_FPS_FORCE must not touch the nominal rate: it is slow motion on screen ([8681f2e](8681f2e))
* **lift:** clear an earlier lift's hot pieces before the patches; count every made and raw output-size place ([816a472](816a472)), closes [NotRexed#912](https://github.com/notquiteog/Hookshot/issues/912)
* **lift:** edit_c in chunks.py, so its tests import without the lift's tools ([1c5258f](1c5258f))
* **lift:** group page marks after the patches; a missing output-size edit stops the lift ([3fa6300](3fa6300))
* **linux:** the AppImage's parts list names the repository it is built in ([2228816](2228816))
* narrow screens: window at the picture's shape, save picture's middle band, comments ([3060261](3060261)), closes [NotRexed#1183](https://github.com/notquiteog/Hookshot/issues/1183)
* preserve NUP member names during implicit reads ([1fdfeff](1fdfeff))
* publish the camera from the inlined projection copies too ([c630d59](c630d59))
* read the upscaler contract in descriptor order, not from draw_sampled ([492bcd0](492bcd0))
* **release:** the fork's releases follow upstream's v2.0.x line ([d495994](d495994))
* **release:** the setup, the Builder and the game update from the repository they were built in ([6fd4364](6fd4364))
* **release:** the Windows release installs the renderer CI built, with DLSS, NR and FSR 4 ([0fa18bd](0fa18bd))
* scan for the view projection once a target set, not once a draw ([12d4d83](12d4d83))
* snapshot the projection's fields, and name what caps the frame rate ([3790556](3790556))
* the conventionalcommits preset has to match the changelog writer ([ca6595f](ca6595f))
* the DLSS contract test links again ([86b6988](86b6988))
* the game's network takes one texture, so it is not a temporal upscaler ([853cfe3](853cfe3))
* the history reset the document specifies, which nothing implemented ([9858599](9858599))
* the host camera symbols must be weak, or the renderer does not load at all ([70ecdf4](70ecdf4))
* the release's commit-analyzer preset is a package, and it was not declared ([3ff9fbd](3ff9fbd))
* the renderer would not load, and the build now says so ([c871a3c](c871a3c))
* the shader check compared glslang's version, and ran where glslang is not ([f48483c](f48483c))
* the view projection scan ran per draw, which stuttered hard ([2d9b4e8](2d9b4e8))
* **vk:** a draw that waits for its pipeline runs the build at once if it is still queued ([aafbaf5](aafbaf5))
* **vk:** DLSS frame generation sets NGX's parameters through its C functions ([2c19b63](2c19b63))
* **vk:** frame generation Auto: the snap distance is not named near (windows.h defines it empty) ([d156a6d](d156a6d))
* **vk:** keep the last sRGB UNORM surface format, so drivers that list only sRGB keep theirs ([3408437](3408437))
* **vk:** Linux/Wayland: take an sRGB surface format for the swapchain ([7416487](7416487))
* **vk:** no mirror is made inside a parallel replay's segment (an earlier segment read it unfilled) ([8f0ed68](8f0ed68))
* **vk:** tag the uplift colour pair as whole resources, as the shipping integration does ([dd3e61e](dd3e61e))
* **vulkan:** bind only bytes accessed by buffer-image copies ([b0a9034](b0a9034))
* **vulkan:** clean up failed mesh argument initialization ([b2f5692](b2f5692))
* **vulkan:** preserve guest mip levels in partial clears ([d7a1207](d7a1207))
* **vulkan:** release modules from rejected cache records ([2838e89](2838e89))
* **widescreen:** the save picture shows the middle 16:9 on ultrawide ([NotRexed#952](https://github.com/notquiteog/Hookshot/issues/952)) ([e2e155e](e2e155e))

### Performance Improvements

* **fpx:** keep the hardware result for FMUL/FMLA S when the flags are already set ([6de3bf1](6de3bf1))
* **lift:** one page mark per straight-line group of stores through a base register ([825861b](825861b))
* **lift:** the interrupt poll before each backward goto, calling a64_poll_cur() with no argument, so a loop's guest registers stay in host registers ([0d6ed03](0d6ed03))
* stream sampled profiles instead of loading every line ([8cf6d1e](8cf6d1e))
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