Files
d4r/docs/windows-fsr-rr.md
T
ayo ac4ee6c225 Expand Windows GPU support, add native FSR RR, and improve SR interop
Add a native D3D12 FSR Ray Regeneration route for gfx1200/gfx1201,
using the game's device and queue with embedded HLSL guide preparation
and output composition. Preserve the vkd3d compatibility route for
RDNA3 and expose explicit route overrides and diagnostics.

Improve Windows runtime compatibility:
- Retry rejected application-ID initialization with a project identity.
- Defer expensive RR backend creation until feature creation.
- Recover from transient submission failures without disabling later frames.
- Add shared-buffer export fallbacks through explicit heaps and a private
  device on the same adapter.
- Safely evict old RR descriptor sets when input views change.
- Treat requested RR presets as hints when a configured model is available.
- Expand SR/RR input formats and handle diffuse/specular hit distances,
  packed normals, depth-stencil resources, and unused FSR guides.

Reduce SR overhead by batching output downloads and format conversion
under one completion wait. Optimize strict M enc2/tube attention with
bounded FP32 accumulation and the original FP64 fallback for ambiguous
rounding cases.

Extend RDNA4 tooling with gfx1200/gfx1201 native FP8 RR model builds,
capability checks, architecture-aware deployment, and experimental K/M
kernel paths. Require matching model and runtime arithmetic families.

Add optional embedded FSR shader optimizations keyed by shader hash and
exact original SPIR-V contents. Preserve reflected metadata and record
optimizer provenance, artifact hashes, and cache identity.

Simplify Windows installation with automatic backend dependency downloads,
download progress and ETA, backed-up absolute OptiScaler library paths,
and recovery from interrupted RR model builds.

Add build-kernels.bat and a kernels-only build matrix for all eleven
registered RDNA targets, with code-object target validation and SHA256
manifests. Expand conversion probes, numerical stress tests, temporal
benchmark tooling, installer/deployment tests, and setup documentation.

Document synthetic regression and timing results, experimental build
options, and remaining RDNA4 hardware validation requirements.
2026-10-17 07:40:34 -04:00

21 KiB

Native Windows FSR Ray Regeneration

Native D3D12 route for RDNA4 (2026-10-17)

The source now selects native D3D12 on gfx1200/gfx1201 when [RayReconstruction] Backend = fsr. AMD's original denoiser receives the game's real D3D12 device and records on its queue. D3D12 compute shaders perform the guide conversion and composition on that same device. This route does not load the vkd3d core, the amdxc shim, or Vulkan; it does not redirect AMD's driver lookup or apply the RDNA3 compiler workaround.

Guides are copied into shader-readable textures on the same device, including depth resources that deny shader reads. This retains subrect and guide-format handling while removing the shared cross-API packet. Composition goes directly to the internal SR stage, or copies into the game's output without SR. RGBA32F output widens the completed half image on the GPU. Evaluation remains synchronous: one completion wait retires the denoiser list before resource reuse and SR; this is not an asynchronous backend. AMD's denoiser/context may still compile shaders on first use. No RDNA4 timing or quality improvement has been measured.

RDNA3 keeps the existing vkd3d compatibility route below. Diagnostic environment D4R_FSR_RR_DEVICE=auto|native|vkd3d overrides selection; auto is the default. Native provider/context failures are reported explicitly. An override can select the compatibility route for diagnosis. Readiness checks require only AMD's denoiser DLL for native D3D12, and retain the extra payload requirements for vkd3d. The dependency installer permits omitting that payload only for explicitly RDNA4-only packages; mixed packages still require it. The native adapter compiles its embedded conversion shaders with Windows' system d3dcompiler_47.dll.

Validation on the available RX 7700 XT (gfx1101):

  • Six native conversion tests pass, including WARP, hardware depth, depth-stencil with denied shader reads, subrects with sentinel padding, invalid-normal sky passthrough, and exact half-to-float output widening. Each case checks all 4,355 pixels against independent expected values. These tests do not execute AMD's denoiser. Native D3D12 float-to-half stores use their specified truncation; cross-backend bit equality is not asserted.
  • All 22 CTest checks, 13 RR Python tests and 13 installer tests pass.
  • The automatic RDNA3 compatibility route passes 24 moving frames with guide and output format switching, typeless D32S8 depth and exact same-frame widening (build/fsr-native-rdna3-compat-formats-v1/). Another 24 frames pass dynamic render extents, retained creation-sized resources, SR-bound rejection and SR output (build/fsr-native-rdna3-compat-dynamic-sr-v1/).
  • Explicit native selection on gfx1101 without the compatibility DLLs reaches AMD's provider query and returns no FSR Ray Regeneration provider for this device, as expected. It does not silently switch routes.
  • The gfx1200/gfx1201 shims and gfx1201 native conversion probe compile successfully. Full native RDNA4 denoising, SR handoff, temporal quality, real games and performance still require hardware validation. Existing installed packages were not replaced.

Run the conversion checks with:

cmake --build BUILD --target d4r_fsr_rr_native_probe
ctest --test-dir BUILD -R fsr_rr_native --output-on-failure

For an RDNA4 synthetic integration run, use a matching target shim/probe and tools/windows/test_fsr_rr_ngx.py --device native with its usual fixture/runtime arguments. That mode removes compatibility DLLs from the private staged runtime and verifies the logged route. Include ordinary SR and dynamic-resolution runs before claiming end-to-end validation.

Status (2026-10-17): runs on RDNA3 through vkd3d-proton; synthetic validation only

[RayReconstruction] Backend = fsr answers a game's DLSS RR request (NGX feature 13) with AMD's FSR Ray Regeneration 1.2 denoiser, then upscales with an internal Super Sampling feature. Selection, install.bat [F] and d4r\fsr payload are described in WINDOWS_README.txt. No game exposes RR to test with, so everything below is a synthetic scene; game compatibility and image quality against DLSS RR are unverified.

Why the earlier "zero providers" result was correct: AMD's denoiser ships only wmma_fp8 shaders, issued through AMD's driver extension (AmdExtD3DShaderIntrinsicsUAV), and RDNA3 has no FP8 WMMA hardware. Native Windows drivers therefore offer no provider. Linux gets one through vkd3d-proton with dxil-spirv's scalar FP8 fallback and a Proton amdxc64.dll shim. The Windows route reproduces that:

  1. vkd3d-proton for Windows (tools/windows/build_fsr_payload.py): vkd3d-proton 3dfc6f07 plus dxil-spirv PR 307 (f9f6294f) and -Denable_extended_emulation=true, built with llvm-mingw clang; local Windows patches in patches/vkd3d-proton-windows/. The shipped d4r\fsr\d4r_vkd3d_core.dll is d3d12core.dll renamed and loaded by path; IVKD3DCoreInterface::CreateDevice is called directly (the IDL has no base interface, so it is vtable slot 0). DXIL_SPIRV_CONFIG=wmma_rdna3_workaround is set in-process.
  2. amdxc64 shim (tools/windows/amdxc64_shim): recovered from the denoiser's call sites (interface IDs {014937ec-…} factory, {f714e11a-…} wave-matrix properties with a 16x16x16 type-0xb entry, {ba019d53-…} intrinsics enable, {b58d6601-…} device info). It advertises the FP8 shape the shaders use; the arithmetic is emulated by dxil-spirv. The device-info values are placeholders. It never forwards to AMD's driver.
  3. Lookup redirect. The denoiser finds amdxc64.dll with GetModuleHandle. In a game process AMD's own D3D12 driver has already loaded the real amdxc64.dll, which crashes on a vkd3d device. The loaded copy of AMD's DLL gets its GetModuleHandleA/W imports redirected in memory to the shim. The DLL on disk is not modified.

Data path (tools/windows/fsr_rr_backend.cpp): the game queue copies the guides into the same shared D3D12 packet the native DLSS backend uses; the packet is imported into vkd3d's VkDevice; fsr_rr_prepare and fsr_rr_compose (Vulkan, recorded through vkd3d's DXVK interop) convert DLSS-RR guides to FSR inputs and back around ffxDispatch; the composed colour returns through the packet to a game-device texture, and the internal SR feature upscales it into the game's output. Every hand-off is CPU-synchronized; no vkd3d patch is needed.

Evidence (all synthetic, RX 7700 XT, static camera, zero jitter):

  • Probe (fsr_rr_probe, 960x540, 4 signals): provider found, context created, 48 frames finite, denoised, reset handled; GPU median 20.3 ms (build/fsr-vkd3d-probe-v1/out3).
  • NGX feature-13 path (tools/windows/test_fsr_rr_ngx.py, noisy scene with a known noise-free answer): 640x360 render=output without SR, RMSE vs clean 0.488 -> 0.060 (build/fsr-rr-shim-run-v9); 640x360 -> 1280x720 with SR from the extracted v9 package, RMSE 0.289 against 0.581 for the noisy input and 0.314 for the noise-free render-size image nearest-upsampled (build/fsr-rr-ngx-upscale-v9-v1). About 10-11 ms per frame (convert+denoise+compose ~7 ms, SR submit ~2.6 ms); first RR start compiles AMD's shaders (~65 s cold).
  • Native DLSS RR regression with the same shim: PASS NGX RR ... standalone_exact=1.

Guide conversion check: a diagnostic (D4R_FSR_RR_DUMP_DIR, first D4R_FSR_RR_DUMP_FRAMES frames) dumps the converted linear depth, motion and normals. On the moving fixture the converted motion equals the ground-truth PreviousUV - CurrentUV (FFX's contract) to 1.5e-6, the view-z delta is 0 for lateral motion, and linear depth is reconstructed to 5e-7 (build/fsr-rr-dump-moving-v2).

Motion vectors matter, measured (FSR alone, same-size, moving camera, RMSE vs noise-free): with AMD's default stability bias the ordering was misleading (correct 0.107, flipped 0.066, zeroed 0.158, because default bias over-weights history and rejection scored better); at stability bias 0 it is physically ordered (correct 0.033, flipped 0.042, zeroed 0.061). FsrStabilityBias (D4R_FSR_RR_STABILITY_BIAS), FsrDetail (D4R_FSR_RR_DETAIL), D4R_FSR_RR_KEY_<n>, D4R_FSR_RR_DEMOD_FLOOR and D4R_FSR_RR_HALO_LIMIT follow the Linux adapter. Through the full pipeline (FSR then SR): same size, bias 0 gives 0.036 against 0.107 for auto (3x); with a 2x SR upscale auto gives 0.214 against 0.248 for 0 (auto 14% better). The shipped default is therefore auto.

Timing (RX 7700 XT, synthetic, two direct signals, SR included, per frame, median): 1080p Quality (1280x720 -> 1920x1080) 29.8 ms total, 1440p Quality (1707x960 -> 2560x1440) 50.6 ms total. GPU timestamps (D4R_FSR_RR_LOG) at 1280x720 render: prepare 0.5 ms, AMD's denoiser dispatch 21 ms, compose+copy 0.2 ms: the denoiser itself dominates. Linux reports about 8 ms for AMD's denoiser at 1280x720 on gfx1101 under RADV; the same vkd3d-proton/dxil-spirv stack is about 2.6x slower here, on AMD's Windows Vulkan driver. Optimization parity with Linux is NOT achieved, and the cause (driver code generation for the emulated FP8 shaders, cooperative-matrix use, a missing vkd3d/dxil-spirv option) has not been identified. The 3-4 ms outside the denoiser (packet copies and the CPU-synchronized hand-offs) is also unoptimized.

Limits: output may exceed render size only through the SR stage (without SR, output must equal render size); dynamic render sizes must stay within creation dimensions and the SR library's queried dynamic range; unsupported sizes fail before submission (see the newer dynamic-resolution evidence below); diffuse is a direct signal (no diffuse hit distance); the exposure texture only goes to the SR stage; HDR + render-resolution unjittered motion only; RGBA16F/RGBA32F output without SR; mapped float guide formats and depth planes have focused synthetic coverage, while additional guide encodings remain unsupported; alpha output is not produced; first RR start compiles AMD's shaders (~65 s, cached per game exe in %LOCALAPPDATA%\d4r\cache\vkd3d). Each evaluation blocks the CPU until its GPU work completes (as the native DLSS RR backend does), so it is not asynchronous. No game evidence exists for FSR RR.

Dynamic resolution: SR range identified and enforced (2026-10-17)

The earlier 0xBAD00005 was caused by an invalid SR render size, not a blanket lack of dynamic-resolution support. NVIDIA's own log reports Quality's minimum as half the output dimensions: the previous 640x360 -> 1280x720 fixture tried 576x324, below the 640x360 minimum. Plain SR reproduces that rejection with flags 0 and 11, including reset-every-frame (build/sr-dynamic-isolation-v1/). Valid plain-SR subrects pass 24 synthetic frames with creation 426x256, output 640x384 and smaller render 384x224 (build/sr-dynamic-isolation-v2/summary.json).

The internal SR feature now queries DLSSOptimalSettingsCallback using NVIDIA's helper contract. FSR intersects the returned range with its existing creation-size bounds and rejects invalid sizes synchronously before recording a command boundary. A missing or invalid query retains the previous creation-size-only range. No ratio is hardcoded in production. The plain-SR probe validates/logs its requested subrect; the FSR wrapper checks every reported extent and SR submission count. --sr-bounds tests each axis below the pinned Quality minimum, restores valid dimensions, and completes subsequent evaluations on the same command list.

Moving-camera FSR+SR: creation 640x360, output 960x540, render sizes 640x360 / 576x324 / 480x270 and back, 24 frames per run. Both retained creation-sized textures and resized textures pass, without feature recreation. Queried SR range is 480x270..960x540; creation dimensions still cap this FSR feature at 640x360. Evidence: build/fsr-rr-valid-dynamic-maxres-bounds-v1/summary.json and build/fsr-rr-valid-dynamic-resized-bounds-v1/summary.json. Both below-minimum axes reject before submission; all 24 subsequent SR outputs are finite and nonuniform. Tail RMSE is 0.07665 versus noisy 0.48826 and ideal nearest-resampled clean input 0.03894. This is synthetic denoising evidence, not exact/reference-quality parity: maximum error within the sky mask reaches 0.78223. Alpha, other encodings, broader motion and games remain open.

Format coverage and denoiser optimization trials (2026-10-17, later pass)

FSR-only output now accepts RGBA32F, including temporal switching with RGBA16F. Composition still runs at half precision. A checked Vulkan nearest blit widens the completed half image into a lazily allocated float image, then copies it through the existing shared packet. No CPU tensor conversion is added, and no new alpha or FP32-denoiser claim is made. The ordinary FSR+SR path continues to hand SR a half image.

Synthetic moving-camera tests at 640x360 pass 24 frames each: canonical half output; alternating half/float output plus alternating canonical/float guides; and RGBA32F output with typeless D32S8 depth and unchanged stencil. --verify-output-widening compares every final output component against a diagnostic readback of the same frame's composed half image, widening the independent reference where needed. All components match exactly. Converted linear depth, motion and normals also match canonical dumps byte-for-byte across all 24 guide-switch frames. Evidence: build/fsr-formats-{half,switch,d32s8}-composed-v1/. test_fsr_rr_ngx.py --without-sr uses the existing diagnostic environment contract because player startup requires SR dependencies. It is not an installer/player-without-dependencies compatibility claim.

Three denoiser/compiler trials produced no reliable gain and are not promoted: cooperative conversion-loop unrolling, forcing the minimum subgroup size, and retaining the RDNA3 layout workaround without global FP16 RTZ. AMD's denoiser and all quality settings/signals remain unchanged. At 1280x720, these fixtures start near 21.5 ms and reach about 31-32 ms in later frames, so a single whole-run median can mislead. Late-frame timings stay within the control spread. Static and moving noisy fixtures retain the existing finite/nonuniform and RMSE < 0.5 * noisy-input gate. No lower-resolution or skipped-signal shortcut is substituted.

Cross-process FSR output is not bit-exact even for repeated unchanged controls: in the 720p moving fixture, frame 0 has 2,087,117 differing components and maximum absolute difference 0.1435546875 between two controls. Their tail RMSE remains about 0.0612 versus noisy 0.4872. This precludes attributing inter-process image differences solely to a candidate. The same-frame format-conversion gate avoids that ambiguity; K keeps its strict full-frame exact gate. The FSR numerical variability is recorded, not fixed or reclassified as exact. Evidence: build/fsr-perf-720-{baseline-v2,baseline-repeat-v1,unroll-v2,wave32-v1,rte-v1}/ and build/fsr-perf-static-720-{baseline,unroll}-v1/. Experimental binaries remain private under build/; the shipped payload stays build/fsr-payload-v1. The two third-party source files edited for experiments were restored; pre-existing libc++ include fixes remain. Linux denoiser performance parity is still open.

Local shader optimization payload (2026-10-17)

The payload builder now preserves dxil-spirv's reflected shader metadata when replacing compute shaders. An earlier replacement path returned before reflecting the required wave size; even a byte-identical replacement could therefore change the output. Patch 0003-preserve-meta-for-shader-overrides.patch moves all replacement to the existing late path.

Optional local optimizations use --shader-dump DIR --spirv-opt PATH --optimize-shader HASH (repeat the final argument for each measured shader). --shader-variant DIR adds another exact original translation, such as the existing NGX rounding-mode variant, without changing its arithmetic settings. The builder runs spirv-opt --target-env=vulkan1.3 -O, validates each result, and embeds the original and optimized modules in the private core. A replacement requires both the DXIL shader hash and a byte-for-byte match of the original translation. Different translator options, resource layouts, or devices that produce different SPIR-V retain their original translation. No precision-widening flag or change to FP8 conversion/quantization is added.

The source inputs, generated header, optimizer hash, and output hashes are saved in shader-sources/ and fsr-payload.json. The cache build ID incorporates local patches and the optimization manifest, so the new core rejects the old payload's compiled shader cache. No external shader folder or global override environment is needed at runtime. D4R_FSR_DISABLE_BUILTIN_SHADERS=1 selects an unmodified control; use a separate cache directory when comparing this control with enabled replacements.

On this RX 7700 XT, applying -O to every shader helps some layers and hurts others. The local payload selects seven measured winners: 09749b7dfc3a7804, 430ab44dc42ba8b5, 5bec91d51f1d2038, c7a9797f50bb6526, e34e88f998f02da9, e7d13b75c921986c, and f8459f275005d65e. At 1280x720 with two direct signals and NGX's existing arithmetic settings, the probe GPU median is 34.374 -> 32.846 ms (4.4% lower). Full NGX late-frame denoiser medians are 31.58 -> 30.88 ms and 31.81 -> 31.04 ms on a repeat (2.2-2.4% lower). NGX tail RMSE changes by +0.41% / +0.33%, within the existing run-to-run noise; all seven replacements activate. These are synthetic GPU timings, not game FPS. This is a modest Windows optimization, not Linux performance parity. Actual loop expansion and straight-line cooperative-matrix component conversion did not improve the measured result beyond -O and are not included. Full measurements and deployment details are in C:\Users\ayo\Desktop\d4r-windows-fsr-results-20261017-0333\SUMMARY-selected-O.md.

History: the investigation before the vkd3d route

Corrected provider descriptor recheck

The query now passes FFX_API_CREATE_CONTEXT_DESC_TYPE_DENOISER in ffxQueryDescGetVersions.createDescType, rather than its effect ID. AMD's API contract defines this field as the creation description type. A bounded query against the unchanged pinned AMD DLL and real RX 7700 XT still returns OK with zero providers. The adapter reports shader model 6.6. No context is created or denoiser dispatch attempted; FSR remains unavailable. Evidence: build/fsr-rr-correct-descriptor-v1/validation.json. The earlier probe binary is preserved in build/fsr-rr-descriptor-baseline-v1/. No driver, identity, security, GPU setting or game changes occurred.

The native D3D12 provider check currently fails on RX 7700 XT. This does not implement a working Windows FSR RR backend or DLSS RR replacement.

AMD SDK 2.3's unmodified denoiser 1.2.0.2740 loads successfully and accepts the provider query, but returns an empty provider list. The installed driver reports shader model 6.6. No context creation or denoiser dispatch follows this result. The DLL's Authenticode signature was valid, signed by Advanced Micro Devices; its SHA256 matches the Linux reference's tested binary: 48f1e5888ba6a0a3d59a98b9751e37c392b0f7b8c223d0082d5c1f40642879d3.

Linux's RDNA3 route uses vkd3d-proton, dxil-spirv scalar FP8 emulation, and wmma_rdna3_workaround. Those are not native Windows driver features. Native parity requires a compatible shader execution route as well as the DLSS guide conversion and internal SR adapter. Merely importing the Linux backend or selecting Backend=fsr would not supply those pieces.

The DLSS native D/E path already uses Linux's gfx11 shaders and lowering. A read-only comparison against 34e6b0fe71d770729000a553f79b784e9b2dc279 verifies 25 shader/runtime/packer sources after CRLF/LF normalization. Both installer-built D/E models verify all 66 products, 11 native 32-wide stages and the translated 64-wide output stage. Evidence: build/rr-rdna3-source-parity-v1.json. This establishes source/recipe parity for native DLSS RR only. It provides no FSR dispatch, FSR optimization parity or measured GPU-time parity. FSR's missing native execution route remains a separate prerequisite.

Reproduce without a game

./scripts/windows/setup-fsr-rr.ps1
cmake --build build/native-engine-windows-gfx1101 --target d4r_fsr_rr_probe
./scripts/windows/test-fsr-rr.ps1 `
  -Probe build/native-engine-windows-gfx1101/d4r_fsr_rr_probe.exe `
  -DenoiserDll .tools/fsr-rr-1.2/amd_fidelityfx_denoiser_dx12.dll `
  -OutputDirectory build/fsr-rr-new-query -QueryOnly

Use a new output folder under ignored build/. The wrapper pins the DLL hash, captures stdout/stderr, limits execution to 120 seconds, and propagates failure. Without -QueryOnly, the harness proceeds only if a provider exists: it measures 48 synthetic frames with static/moving cameras and a reset against noise-free signals. A successful query alone is not execution or image-quality evidence. The Windows adapter helper queries the device's DXGI identity. It does not load Proton driver shims or modify drivers, GPU settings, or security policy.

Source provenance

tools/fsr_rr_probe.cpp, tools/fsr_rr_scene.h, tools/d4r_fsr_rr.h, and the MIT-licensed FidelityFX API headers were imported from Linux reference 34e6b0fe71d770729000a553f79b784e9b2dc279. The probe includes the native Windows identity helper instead of Linux's sysfs/vkd3d identity helper. AMD's DLL stays under ignored .tools/ and is not included in source or release packages.

Evidence: build/fsr-rr-native-wrapper-query/probe.json. No game was touched.