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.
This commit is contained in:
ayo committed 2026-10-17 07:40:34 -04:00
1 parent a617c818de
commit ac4ee6c225
67 files changed
+3147 -326

No files matched your search

+195
View File
@@ -0,0 +1,195 @@
// Native D3D12 counterpart of fsr_rr_prepare.comp. Keep conversion semantics aligned.
// FSR Ray Regeneration backend, stage 1: a game's DLSS Ray Reconstruction inputs -> the inputs of
// AMD's FidelityFX denoiser 1.2 (ffx_denoiser.h), at render resolution.
//
// The split of the game's single noisy colour into a diffuse and a specular signal follows the
// DLSS-RR -> FSR-RR adapter of Zach Hembree's OptiScaler branch ffx-denoise-experimental (GPL-3.0,
// 18ac686, FSRDInputConv.hlsl, "Mode 2") and burak113's continuation (5a6adb1): each lobe gets the
// colour in proportion to its albedo and is demodulated by it. That is an approximation - one
// colour cannot be split exactly - and what does not survive demodulation goes to the residual,
// which bypasses the denoiser. Unlike that reference this writes FSR-RR 1.2's contract: signed
// linear depth reconstructed from hardware or linear depth, motion as previous-minus-current UV,
// linear (non-gamma) albedo and the colour before particles as the denoised base.
Texture2D<float4> inColor : register(t0);
Texture2D<float4> inDepth : register(t1);
Texture2D<float4> inMotion : register(t2);
Texture2D<float4> inNormals : register(t3);
Texture2D<float4> inRoughness : register(t4); // the normals again when roughness is packed
Texture2D<float4> inDiffuseAlbedo : register(t5);
Texture2D<float4> inSpecularAlbedo : register(t6);
Texture2D<float4> inSpecularHit : register(t7); // bound to the normals when absent
Texture2D<float4> inDiffuseHit : register(t8); // bound to the normals when absent
Texture2D<float4> inBeforeParticles : register(t9); // bound to the colour when absent
RWTexture2D<float> outLinearDepth : register(u0);
RWTexture2D<float4> outMotion : register(u1);
RWTexture2D<float4> outNormals : register(u2);
RWTexture2D<float4> outDiffuseAlbedo : register(u3);
RWTexture2D<float4> outSpecularAlbedo : register(u4);
RWTexture2D<float4> outDiffuse : register(u5);
RWTexture2D<float4> outSpecular : register(u6);
RWTexture2D<float4> outResidual : register(u7);
// Matrices are uploaded as the row-major, row-vector (v' = vM) float[16] NGX and FFX use. Read as a
// GLSL (column-major) column_major float4x4 that is the transpose, so M * v here is the row-vector product v * M.
struct FsrRrConstants
{
column_major float4x4 clipToView; // inverse(ViewToClipMatrix)
column_major float4x4 viewToPreviousView; // inverse(WorldToView) * previous WorldToView
float4 renderSize; // width, height, 1 / width, 1 / height
float4 motion; // xy: MV texel -> UV delta (MV.Scale / MV resolution); zw: MV texel per render pixel
uint4 colorDepthBase; // colour subrect origin xy, depth subrect origin xy
uint4 motionBaseFlags; // motion subrect origin xy, flags, unused
float4 depthParams; // x: sign of view z in front of the camera, y: sky depth (hardware), z: far marker
float4 detail; // x: share of the colour that bypasses the denoiser (0: none); y: smallest albedo a
// signal is divided by
};
cbuffer Constants : register(b0) { FsrRrConstants pc; }
static const uint kHardwareDepth = 1u;
static const uint kRoughnessPacked = 2u;
static const uint kSpecularHit = 4u;
static const uint kSpecularHitInW = 8u; // DLSSD.SpecularRayDirectionHitDistance: the distance is .w
static const uint kDiffuseHit = 16u;
static const uint kDiffuseHitInW = 32u;
static const uint kNormalsUnorm = 64u; // [0,1]-encoded normals (UNORM formats)
static const uint kBeforeParticles = 128u;
static const uint kInvalidNormalPassthrough = 256u;
static const float kHalfMax = 65000.0;
float3 safe(float3 v)
{
// NaN fails both comparisons and becomes 0
return float3(v.x >= 0.0 ? min(v.x, kHalfMax) : 0.0, v.y >= 0.0 ? min(v.y, kHalfMax) : 0.0,
v.z >= 0.0 ? min(v.z, kHalfMax) : 0.0);
}
// AMD's NormalToOctahedronUv (FidelityFX denoising.md 5.3.1)
float2 octahedron(float3 n)
{
n /= abs(n.x) + abs(n.y) + abs(n.z);
float2 k = float2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);
float2 xy = n.z < 0.0 ? (1.0 - abs(n.yx)) * k : n.xy;
return xy * 0.5 + 0.5;
}
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
const int2 px = int2(tid.xy);
if (px.x >= int(pc.renderSize.x) || px.y >= int(pc.renderSize.y))
return;
const uint flags = pc.motionBaseFlags.z;
const float2 uv = (float2(px) + 0.5) * pc.renderSize.zw;
const float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
// Signed view-space position. Hardware depth goes through the inverse projection, which carries
// the game's handedness and (reversed) depth range; linear depth is the distance along the view
// axis, given its sign by the projection.
const float depth = inDepth.Load(int3(int2(pc.colorDepthBase.zw) + px, 0)).r;
float3 viewPos;
bool sky;
if ((flags & kHardwareDepth) != 0u)
{
const float4 p = mul(pc.clipToView, float4(ndc, depth, 1.0));
viewPos = p.xyz / p.w;
sky = depth == pc.depthParams.y || !(abs(p.w) > 0.0);
}
else
{
const float4 p = mul(pc.clipToView, float4(ndc, 0.5, 1.0));
const float3 ray = p.xyz / p.w;
const float z = depth * pc.depthParams.x;
viewPos = ray * (z / ray.z);
sky = !(depth > 0.0) || depth >= pc.depthParams.z;
}
const float3 color = safe(inColor.Load(int3(int2(pc.colorDepthBase.xy) + px, 0)).rgb);
const float3 base = (flags & kBeforeParticles) != 0u ? safe(inBeforeParticles.Load(int3(px, 0)).rgb) : color;
// Particles (colour after minus before) stay out of the denoiser; signed, so smoke can darken.
const float3 particles = color - base;
float3 diffuseAlbedo = clamp(inDiffuseAlbedo.Load(int3(px, 0)).rgb, 0.0, 1.0);
float3 specularAlbedo = clamp(inSpecularAlbedo.Load(int3(px, 0)).rgb, 0.0, 1.0);
// Energy above 1 is taken from the diffuse lobe first (reference: albedo overshoot clamp).
diffuseAlbedo = max(diffuseAlbedo - max(diffuseAlbedo + specularAlbedo - 1.0, 0.0), 0.0);
const float albedoSum = dot(diffuseAlbedo + specularAlbedo, ((float3)1.0));
sky = sky || !(albedoSum > 3e-3) || any(isnan(viewPos)) || any(isinf(viewPos));
const float4 normalTexel = inNormals.Load(int3(px, 0));
float3 normal = (flags & kNormalsUnorm) != 0u ? normalTexel.xyz * 2.0 - 1.0 : normalTexel.xyz;
const float normalLength = dot(normal, normal);
// A zero/invalid normal is no surface, not an invented world-Z-facing mirror. Alan Wake 2
// supplies zero normals for its sky with finite FP16-max depth, so the far bound alone misses it.
if ((flags & kInvalidNormalPassthrough) != 0u)
sky = sky || !(normalLength > 1e-8) || isnan(normalLength) || isinf(normalLength);
if (sky)
{
// Outside linearDepthBounds: the denoiser passes these texels through, and the residual carries
// the whole colour.
outLinearDepth[px] = float(pc.depthParams.z * 10.0 * pc.depthParams.x);
outMotion[px] = ((float4)0.0);
outNormals[px] = float4(0.5, 0.5, 1.0, 0.0);
outDiffuseAlbedo[px] = ((float4)0.0);
outSpecularAlbedo[px] = ((float4)0.0);
outDiffuse[px] = ((float4)0.0);
outSpecular[px] = ((float4)0.0);
outResidual[px] = float4(color, 0.0);
return;
}
outLinearDepth[px] = float(viewPos.z);
// 2.5D motion: DLSS motion vectors point from this pixel to where it was (previous - current), in
// the MV texture's pixels. The depth delta is the camera's: the previous view's z of this point
// (moving geometry is not reprojected; there is no previous depth for it).
const int2 mvTexel = int2(pc.motionBaseFlags.xy) + int2(float2(px) * pc.motion.zw);
const float2 mv = inMotion.Load(int3(mvTexel, 0)).xy * pc.motion.xy;
const float4 previous = mul(pc.viewToPreviousView, float4(viewPos, 1.0));
outMotion[px] = float4(mv, previous.z / previous.w - viewPos.z, 0.0);
normal = dot(normal, normal) > 1e-8 ? normalize(normal) : float3(0.0, 0.0, 1.0);
const float roughness = clamp((flags & kRoughnessPacked) != 0u ? normalTexel.w : inRoughness.Load(int3(px, 0)).r,
0.0, 1.0);
// material type 0 (the default material) everywhere
outNormals[px] = float4(octahedron(normal), roughness, 0.0);
outDiffuseAlbedo[px] = float4(diffuseAlbedo, 0.0);
outSpecularAlbedo[px] = float4(specularAlbedo, 0.0);
// Split by albedo and demodulate (reference "Mode 2"). Per channel, so a tinted metal keeps its tint.
const float3 total = diffuseAlbedo + specularAlbedo;
const float3 specularShare = specularAlbedo / max(total, ((float3)1e-4));
const float3 specularColor = base * specularShare;
const float3 diffuseColor = base - specularColor;
// A dark albedo would turn light it did not shape (fog, sky light, the background at a thin object's edge) into
// a huge signal that the denoiser clips (fog lost) or spreads (bright fringes): no albedo below the floor
// divides a signal, and what the division cannot carry stays in the residual.
float3 diffuse = safe(diffuseColor / max(diffuseAlbedo, ((float3)pc.detail.y)));
float3 specular = safe(specularColor / max(specularAlbedo, ((float3)pc.detail.y)));
if (pc.detail.x > 0.0)
{
// Fine detail the denoiser treats as noise (hair, water ripples): part of the colour goes around it,
// through the residual.
const float denoised = 1.0 - clamp(pc.detail.x, 0.0, 1.0);
diffuse *= denoised;
specular *= denoised;
}
const float3 remodulated = diffuse * diffuseAlbedo + specular * specularAlbedo;
// A: the ray hit distance for an indirect signal (ignored, but non-negative, for a direct one)
float specularHit = 0.0, diffuseHit = 0.0;
if ((flags & kSpecularHit) != 0u)
{
const float4 hit = inSpecularHit.Load(int3(px, 0));
specularHit = clamp((flags & kSpecularHitInW) != 0u ? hit.w : hit.x, 0.0, kHalfMax);
}
if ((flags & kDiffuseHit) != 0u)
{
const float4 hit = inDiffuseHit.Load(int3(px, 0));
diffuseHit = clamp((flags & kDiffuseHitInW) != 0u ? hit.w : hit.x, 0.0, kHalfMax);
}
outDiffuse[px] = float4(diffuse, diffuseHit);
outSpecular[px] = float4(specular, specularHit);
outResidual[px] = float4(base - remodulated + particles, 0.0);
}