Advance native Windows RR integration, kernel tooling and local packaging

Implement native NGX feature-13 RR through a LUID-matched D3D12/Vulkan
shared-memory and timeline-fence bridge. Add owned parameter snapshots,
GPU resource lifetime handling, inherited/enhanced barriers, optional
guides, sampled input formats and depth-plane support with stencil
preservation. Separate RR initialization from the SR CUDA runtime and
expose explicit backend/model selection through player configuration.

Add synthetic bridge, snapshot and NGX lifecycle harnesses covering
camera/history/reset, parameter mutation, delayed submission, early
release, capability gating and unsupported-input rejection.

Add exact gfx11 D/E model generation, verified caches, versioned local
deployment with configuration backups, and isolated portable model
tools with pinned Python, NumPy and glslang dependencies.

Extend Windows K benchmarking and private native-cache selection.
Fix LLVM 22 emitter/helper compatibility and preserve exact-output
validation for local output-kernel optimization.

Add native FSR RR provider diagnostics, local development package
refresh tooling, installer/checker updates, FidelityFX headers and
Windows parity documentation.

Keep NVIDIA binaries, derived models and private optimized kernels out
of distributable packages. Native RR evidence remains synthetic;
game validation, FSR RR dispatch, installer integration and Linux
performance parity remain incomplete.
This commit is contained in:
ayo committed 2026-10-16 12:53:26 -04:00
1 parent 3345fffcd3
commit 247fe1092d
60 files changed
+7168 -66

No files matched your search

+515
View File
@@ -0,0 +1,515 @@
// The synthetic ray-traced scene shared by tools/fsr_rr_probe.cpp (AMD's denoiser alone) and
// tools/rr_backend_check.cpp (a game's NGX Ray Reconstruction request through the shim): a camera over
// a checkered floor, three spheres and a wall, with smooth noise-free lighting signals and one-sample
// white-noise versions of them, every guide the denoisers read, and exact 2.5D motion. Because the
// noise-free signals are known, outputs are compared against ground truth rather than another
// denoiser.
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <vector>
namespace fsr_scene
{
// --- small math -----------------------------------------------------------------
struct V3
{
float x, y, z;
};
inline V3 operator+(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
inline V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
inline V3 operator*(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
inline V3 mul(V3 a, V3 b) { return {a.x * b.x, a.y * b.y, a.z * b.z}; }
inline float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
inline V3 cross(V3 a, V3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
inline V3 normalize(V3 a) { return a * (1.0f / std::sqrt(dot(a, a))); }
inline float saturate(float v) { return std::min(1.0f, std::max(0.0f, v)); }
inline uint16_t to_half(float value)
{
uint32_t bits;
std::memcpy(&bits, &value, 4);
const uint32_t sign = (bits >> 16) & 0x8000u;
int exponent = static_cast<int>((bits >> 23) & 0xff) - 127 + 15;
uint32_t mantissa = bits & 0x7fffffu;
if (((bits >> 23) & 0xff) == 0xff)
return static_cast<uint16_t>(sign | 0x7c00u | (mantissa ? 0x200u : 0u));
if (exponent >= 31)
return static_cast<uint16_t>(sign | 0x7bffu); // clamp to the largest finite half
if (exponent <= 0)
{
if (exponent < -10)
return static_cast<uint16_t>(sign);
mantissa |= 0x800000u;
const uint32_t shift = static_cast<uint32_t>(14 - exponent);
uint32_t half = mantissa >> shift;
if ((mantissa >> (shift - 1)) & 1u)
++half;
return static_cast<uint16_t>(sign | half);
}
uint32_t half = sign | (static_cast<uint32_t>(exponent) << 10) | (mantissa >> 13);
if (mantissa & 0x1000u)
++half;
return static_cast<uint16_t>(half);
}
inline float from_half(uint16_t half)
{
const uint32_t sign = (half & 0x8000u) << 16;
uint32_t exponent = (half >> 10) & 0x1fu;
uint32_t mantissa = half & 0x3ffu;
uint32_t bits;
if (exponent == 0)
{
if (mantissa == 0)
bits = sign;
else
{
exponent = 127 - 15 + 1;
while ((mantissa & 0x400u) == 0)
{
mantissa <<= 1;
--exponent;
}
bits = sign | (exponent << 23) | ((mantissa & 0x3ffu) << 13);
}
}
else if (exponent == 31)
bits = sign | 0x7f800000u | (mantissa << 13);
else
bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13);
float value;
std::memcpy(&value, &bits, 4);
return value;
}
// Row-major, row-vector (v' = vM) 4x4, the convention ffx_denoiser.h asks for.
struct M4
{
float m[4][4];
};
inline M4 view_matrix(V3 eye, V3 target)
{
const V3 forward = normalize(target - eye);
const V3 right = normalize(cross({0, 1, 0}, forward));
const V3 up = cross(forward, right);
M4 v = {};
v.m[0][0] = right.x, v.m[0][1] = up.x, v.m[0][2] = forward.x;
v.m[1][0] = right.y, v.m[1][1] = up.y, v.m[1][2] = forward.y;
v.m[2][0] = right.z, v.m[2][1] = up.z, v.m[2][2] = forward.z;
v.m[3][0] = -dot(eye, right), v.m[3][1] = -dot(eye, up), v.m[3][2] = -dot(eye, forward), v.m[3][3] = 1;
return v;
}
inline M4 projection_matrix(float fovY, float aspect, float zNear, float zFar)
{
const float ys = 1.0f / std::tan(fovY * 0.5f);
M4 p = {};
p.m[0][0] = ys / aspect;
p.m[1][1] = ys;
p.m[2][2] = zFar / (zFar - zNear);
p.m[2][3] = 1;
p.m[3][2] = -zNear * zFar / (zFar - zNear);
return p;
}
inline void transform(const M4& m, V3 v, float out[4])
{
for (int c = 0; c < 4; ++c)
out[c] = v.x * m.m[0][c] + v.y * m.m[1][c] + v.z * m.m[2][c] + m.m[3][c];
}
// --- the synthetic scene ----------------------------------------------------------
struct Camera
{
V3 eye, target;
};
struct Sphere
{
V3 center;
float radius;
V3 base;
float roughness, metal;
};
inline const Sphere kSpheres[] = {
{{0.0f, 0.2f, 6.0f}, 1.2f, {0.8f, 0.3f, 0.2f}, 0.35f, 0.0f},
{{2.4f, -0.3f, 7.5f}, 0.7f, {0.95f, 0.9f, 0.8f}, 0.15f, 1.0f},
{{-2.2f, -0.4f, 5.0f}, 0.6f, {0.2f, 0.5f, 0.85f}, 0.6f, 0.0f},
};
inline constexpr float kGroundY = -1.0f;
inline constexpr float kWallZ = 14.0f;
// Optional near-mirror pond in the floor (rr_backend_check --water): the reflection is the traced scene, with
// guides as Alan Wake 2 hands them for its water (flat diffuse albedo 0.5, specular albedo ~0.96, roughness ~0.047).
inline bool g_water = false;
inline constexpr float kWaterRoughness = 0.047f;
inline constexpr float kWaterMinX = -4.0f, kWaterMaxX = 4.5f, kWaterMinZ = 0.5f, kWaterMaxZ = 4.8f;
inline const V3 kLight = normalize({0.45f, 0.9f, -0.35f});
struct Hit
{
float t = 1e30f;
V3 position{}, normal{}, base{};
float roughness = 1, metal = 0;
bool valid = false, water = false;
};
inline bool hit_sphere(V3 origin, V3 direction, const Sphere& s, float& t)
{
const V3 oc = origin - s.center;
const float b = dot(oc, direction);
const float c = dot(oc, oc) - s.radius * s.radius;
const float h = b * b - c;
if (h < 0)
return false;
const float root = -b - std::sqrt(h);
if (root <= 1e-3f)
return false;
t = root;
return true;
}
inline Hit trace(V3 origin, V3 direction)
{
Hit hit;
float t;
for (const Sphere& s : kSpheres)
if (hit_sphere(origin, direction, s, t) && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = normalize(hit.position - s.center);
hit.base = s.base;
hit.roughness = s.roughness;
hit.metal = s.metal;
hit.valid = true;
}
if (direction.y < -1e-4f)
{
t = (kGroundY - origin.y) / direction.y;
if (t > 1e-3f && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = {0, 1, 0};
const int checker = (static_cast<int>(std::floor(hit.position.x)) + static_cast<int>(std::floor(hit.position.z))) & 1;
hit.base = checker ? V3{0.75f, 0.75f, 0.72f} : V3{0.22f, 0.24f, 0.25f};
hit.roughness = 0.55f;
hit.metal = 0;
hit.valid = true;
const V3 q = hit.position;
if (g_water && q.x > kWaterMinX && q.x < kWaterMaxX && q.z > kWaterMinZ && q.z < kWaterMaxZ)
{
// small world-space ripples, so the reflection has fine detail a denoiser can lose
hit.normal = normalize({0.035f * std::sin(5.1f * q.x + 1.3f * q.z) + 0.02f * std::sin(11.7f * q.z),
1.0f,
0.035f * std::cos(4.3f * q.z - 0.7f * q.x) + 0.02f * std::cos(13.1f * q.x)});
hit.base = {0.5f, 0.5f, 0.5f};
hit.roughness = kWaterRoughness;
hit.water = true;
}
}
}
if (direction.z > 1e-4f)
{
t = (kWallZ - origin.z) / direction.z;
if (t > 1e-3f && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = {0, 0, -1};
hit.base = {0.55f, 0.5f, 0.45f};
hit.roughness = 0.85f;
hit.metal = 0;
hit.valid = true;
}
}
return hit;
}
inline bool shadowed(V3 position, V3 normal)
{
float t;
const V3 origin = position + normal * 2e-3f;
for (const Sphere& s : kSpheres)
if (hit_sphere(origin, kLight, s, t))
return true;
return false;
}
// AMD's ApproximateBRDF (denoising.md 5.5.2, after Ray Tracing Gems ch. 32).
inline V3 approximate_brdf(V3 f0, float alpha, float nov)
{
nov = std::fabs(nov);
const float x[4] = {1, nov, nov * nov, nov * nov * nov};
const float y[4] = {1, alpha, alpha * alpha, alpha * alpha * alpha};
const float m1[2][2] = {{0.99044f, -1.28514f}, {1.29678f, -0.755907f}};
const float m2[3][3] = {{1.0f, 2.92338f, 59.4188f}, {20.3225f, -27.0302f, 222.592f}, {121.563f, 626.13f, 316.627f}};
const float m3[2][2] = {{0.0365463f, 3.32707f}, {9.0632f, -9.04756f}};
const float m4[3][3] = {{1.0f, 3.59685f, -1.36772f}, {9.04401f, -16.3174f, 9.22949f}, {5.56589f, 19.7886f, -20.2123f}};
// HLSL mul(M, v) with M row-major: (M v)_i = sum_j M[i][j] v_j
const float a0 = m1[0][0] * x[0] + m1[0][1] * x[1], a1 = m1[1][0] * x[0] + m1[1][1] * x[1];
const float xw[3] = {x[0], x[1], x[3]}, yw[3] = {y[0], y[1], y[3]};
float b[3];
for (int i = 0; i < 3; ++i)
b[i] = m2[i][0] * xw[0] + m2[i][1] * xw[1] + m2[i][2] * xw[2];
const float c0 = m3[0][0] * x[0] + m3[0][1] * x[1], c1 = m3[1][0] * x[0] + m3[1][1] * x[1];
const float xz[3] = {x[0], x[2], x[3]};
float d[3];
for (int i = 0; i < 3; ++i)
d[i] = m4[i][0] * xz[0] + m4[i][1] * xz[1] + m4[i][2] * xz[2];
float bias = (a0 * y[0] + a1 * y[1]) / (b[0] * yw[0] + b[1] * yw[1] + b[2] * yw[2]);
const float scale = (c0 * y[0] + c1 * y[1]) / (d[0] * yw[0] + d[1] * yw[1] + d[2] * yw[2]);
bias *= saturate(f0.y * 50);
const float s = std::max(0.0f, scale), bb = std::max(0.0f, bias);
return {f0.x * s + bb, f0.y * s + bb, f0.z * s + bb};
}
inline V3 reflect_dir(V3 direction, V3 normal) { return direction - normal * (2 * dot(direction, normal)); }
inline V3 environment(V3 direction)
{
const float up = saturate(direction.y * 0.5f + 0.5f);
return V3{0.35f, 0.45f, 0.6f} * (0.4f + 1.2f * up);
}
inline uint32_t hash32(uint32_t x)
{
// lowbias32, as AMD's guide recommends for white noise
x ^= x >> 16;
x *= 0x7feb352du;
x ^= x >> 15;
x *= 0x846ca68bu;
x ^= x >> 16;
return x;
}
inline float uniform(uint32_t pixel, uint32_t frame, uint32_t stream)
{
return (hash32(pixel * 0x9e3779b9u ^ hash32(frame * 4u + stream + 0x1234567u)) >> 8) * (1.0f / 16777216.0f);
}
enum Signal
{
kDirectDiffuse,
kDirectSpecular,
kIndirectDiffuse,
kIndirectSpecular,
kSignalCount
};
// FfxApiDenoiserSignalFlags of each signal (DIRECT_DIFFUSE 2, DIRECT_SPECULAR 4, INDIRECT_DIFFUSE 16,
// INDIRECT_SPECULAR 32)
inline const uint32_t kSignalFlags[kSignalCount] = {1u << 1, 1u << 2, 1u << 4, 1u << 5};
inline const char* const kSignalNames[kSignalCount] = {"direct_diffuse", "direct_specular", "indirect_diffuse",
"indirect_specular"};
struct Frame
{
// FSR Ray Regeneration's own inputs (fsr_rr_probe)
std::vector<float> depth; // signed linear (view z)
std::vector<uint16_t> motion; // RGBA16F: prevUV - curUV, prev z - cur z
std::vector<uint32_t> normals; // R10G10B10A2: oct normal, roughness, material 0
std::vector<uint32_t> diffuseAlbedo, specularAlbedo; // RGBA8, sqrt encoded
std::vector<float> clean[kSignalCount]; // RGB
std::vector<uint16_t> noisy[kSignalCount]; // RGBA16F
std::vector<uint8_t> valid;
std::vector<V3> diffuseLinear, specularLinear;
// What a DLSS Ray Reconstruction game hands NGX (rr_backend_check)
std::vector<uint16_t> color; // RGBA16F: the noisy signals remodulated and summed, sky added
std::vector<V3> colorClean; // the same from the noise-free signals: the ground truth
std::vector<float> hardwareDepth; // D32: NDC z of the projection (1 for the sky)
std::vector<uint16_t> motionPixels; // RG16F: (previous - current) position in render pixels
std::vector<uint16_t> normalsRoughness; // RGBA16F: signed world normal, w = linear roughness
std::vector<uint32_t> diffuseAlbedoLinear, specularAlbedoLinear; // RGBA8 UNORM, linear
};
inline uint32_t pack_unorm8(V3 v)
{
auto q = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 255.0f)); };
return q(v.x) | (q(v.y) << 8) | (q(v.z) << 16) | (255u << 24);
}
inline uint32_t pack_normal(V3 n, float roughness)
{
// AMD's NormalToOctahedronUv (denoising.md 5.3.1)
const float l1 = std::fabs(n.x) + std::fabs(n.y) + std::fabs(n.z);
float x = n.x / l1, y = n.y / l1;
if (n.z < 0)
{
const float ox = (1.0f - std::fabs(y)) * (x >= 0 ? 1.0f : -1.0f);
const float oy = (1.0f - std::fabs(x)) * (y >= 0 ? 1.0f : -1.0f);
x = ox, y = oy;
}
auto q10 = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 1023.0f)); };
return q10(x * 0.5f + 0.5f) | (q10(y * 0.5f + 0.5f) << 10) | (q10(roughness) << 20) | (0u << 30);
}
// Renders one frame. (jitterX, jitterY): the sample offset from each pixel's centre in pixels (DLSS's
// Jitter.Offset is its negation). Motion is that of the sampled surface point.
inline void render(Frame& frame, uint32_t width, uint32_t height, const Camera& camera, const Camera& previous,
const M4& projection, uint32_t frameIndex, float jitterX = 0.0f, float jitterY = 0.0f)
{
const size_t count = static_cast<size_t>(width) * height;
frame.depth.assign(count, 0);
frame.motion.assign(count * 4, 0);
frame.normals.assign(count, 0);
frame.diffuseAlbedo.assign(count, 0);
frame.specularAlbedo.assign(count, 0);
frame.valid.assign(count, 0);
frame.diffuseLinear.assign(count, V3{});
frame.specularLinear.assign(count, V3{});
frame.color.assign(count * 4, 0);
frame.colorClean.assign(count, V3{});
frame.hardwareDepth.assign(count, 1.0f);
frame.motionPixels.assign(count * 2, 0);
frame.normalsRoughness.assign(count * 4, 0);
frame.diffuseAlbedoLinear.assign(count, 0);
frame.specularAlbedoLinear.assign(count, 0);
for (int s = 0; s < kSignalCount; ++s)
{
frame.clean[s].assign(count * 3, 0);
frame.noisy[s].assign(count * 4, 0);
}
const M4 view = view_matrix(camera.eye, camera.target);
const M4 previousView = view_matrix(previous.eye, previous.target);
const V3 forward = normalize(camera.target - camera.eye);
const V3 right = normalize(cross({0, 1, 0}, forward));
const V3 up = cross(forward, right);
const float tanX = 1.0f / projection.m[0][0], tanY = 1.0f / projection.m[1][1];
for (uint32_t py = 0; py < height; ++py)
for (uint32_t px = 0; px < width; ++px)
{
const size_t i = static_cast<size_t>(py) * width + px;
const float u = (px + 0.5f + jitterX) / width, v = (py + 0.5f + jitterY) / height;
const float ndcX = u * 2 - 1, ndcY = 1 - v * 2;
const V3 direction = normalize(forward + right * (ndcX * tanX) + up * (ndcY * tanY));
const Hit hit = trace(camera.eye, direction);
const uint32_t pixelId = static_cast<uint32_t>(i);
frame.color[i * 4 + 3] = to_half(1.0f);
frame.normalsRoughness[i * 4 + 2] = to_half(-1.0f);
frame.normalsRoughness[i * 4 + 3] = to_half(1.0f);
if (!hit.valid)
{
// Sky: outside linearDepthBounds, so the runtime passes it through.
frame.depth[i] = 5000.0f;
frame.normals[i] = pack_normal({0, 0, -1}, 1);
const V3 sky = environment(direction);
frame.colorClean[i] = sky;
frame.color[i * 4 + 0] = to_half(sky.x);
frame.color[i * 4 + 1] = to_half(sky.y);
frame.color[i * 4 + 2] = to_half(sky.z);
continue;
}
frame.valid[i] = 1;
float clip[4];
transform(view, hit.position, clip);
frame.depth[i] = clip[2];
float projected[4];
transform(projection, {clip[0], clip[1], clip[2]}, projected);
frame.hardwareDepth[i] = projected[2] / projected[3];
float prevView[4], prevClip[4];
transform(previousView, hit.position, prevView);
const M4& p = projection;
const V3 pv = {prevView[0], prevView[1], prevView[2]};
transform(p, pv, prevClip);
const float prevU = (prevClip[0] / prevClip[3]) * 0.5f + 0.5f;
const float prevV = 0.5f - (prevClip[1] / prevClip[3]) * 0.5f;
frame.motion[i * 4 + 0] = to_half(prevU - u);
frame.motion[i * 4 + 1] = to_half(prevV - v);
frame.motion[i * 4 + 2] = to_half(prevView[2] - clip[2]);
frame.motion[i * 4 + 3] = to_half(0);
frame.motionPixels[i * 2 + 0] = to_half((prevU - u) * width);
frame.motionPixels[i * 2 + 1] = to_half((prevV - v) * height);
const V3 diffuse = hit.base * (1.0f - hit.metal);
const V3 f0 = V3{0.04f, 0.04f, 0.04f} * (1.0f - hit.metal) + hit.base * hit.metal;
const V3 toEye = normalize(camera.eye - hit.position);
const float nov = std::max(dot(hit.normal, toEye), 1e-3f);
const V3 specular = hit.water ? V3{0.96f, 0.96f, 0.96f} : approximate_brdf(f0, hit.roughness * hit.roughness, nov);
frame.diffuseLinear[i] = diffuse;
frame.specularLinear[i] = specular;
frame.diffuseAlbedo[i] = pack_unorm8({std::sqrt(diffuse.x), std::sqrt(diffuse.y), std::sqrt(diffuse.z)});
frame.specularAlbedo[i] = pack_unorm8({std::sqrt(specular.x), std::sqrt(specular.y), std::sqrt(specular.z)});
frame.diffuseAlbedoLinear[i] = pack_unorm8(diffuse);
frame.specularAlbedoLinear[i] = pack_unorm8(specular);
frame.normals[i] = pack_normal(hit.normal, hit.roughness);
frame.normalsRoughness[i * 4 + 0] = to_half(hit.normal.x);
frame.normalsRoughness[i * 4 + 1] = to_half(hit.normal.y);
frame.normalsRoughness[i * 4 + 2] = to_half(hit.normal.z);
frame.normalsRoughness[i * 4 + 3] = to_half(hit.roughness);
// Demodulated (albedo-free) clean signals, smooth in world space.
const float visibility = shadowed(hit.position, hit.normal) ? 0.0f : 1.0f;
const float ndl = std::max(dot(hit.normal, kLight), 0.0f);
const V3 reflected = direction - hit.normal * (2 * dot(direction, hit.normal));
const float rdl = std::max(dot(reflected, kLight), 0.0f);
const float glossExponent = 2.0f / (hit.roughness * hit.roughness * hit.roughness * hit.roughness + 0.02f);
V3 clean[kSignalCount];
clean[kDirectDiffuse] = V3{1.0f, 0.95f, 0.85f} * (2.5f * ndl * visibility);
clean[kDirectSpecular] = V3{1.0f, 0.95f, 0.85f} * std::min(8.0f, 3.0f * std::pow(rdl, glossExponent) * visibility * (ndl > 0));
const float wobble = 0.12f * std::sin(1.7f * hit.position.x) * std::sin(1.3f * hit.position.z);
clean[kIndirectDiffuse] = environment(hit.normal) * (0.6f + wobble);
clean[kIndirectSpecular] = environment(reflected) * (0.8f + 0.2f * (1 - hit.roughness));
if (hit.water)
{
// The pond's light is its reflection: the traced scene, lit noise-free. Its diffuse lobe is
// nearly dark despite the 0.5 guide albedo.
clean[kDirectDiffuse] = clean[kDirectDiffuse] * 0.01f;
clean[kIndirectDiffuse] = clean[kIndirectDiffuse] * 0.01f;
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
if (bounce.valid && !bounce.water)
{
const float bounceVisibility = shadowed(bounce.position, bounce.normal) ? 0.0f : 1.0f;
const float bounceNdl = std::max(dot(bounce.normal, kLight), 0.0f);
const V3 light = V3{1.0f, 0.95f, 0.85f} * (2.5f * bounceNdl * bounceVisibility) +
environment(bounce.normal) * 0.6f;
clean[kIndirectSpecular] = mul(bounce.base * (1.0f - bounce.metal), light) +
environment(reflect_dir(reflected, bounce.normal)) * (0.04f + 0.9f * bounce.metal);
}
else
clean[kIndirectSpecular] = environment(reflected);
}
V3 combined{}, combinedClean{};
for (int s = 0; s < kSignalCount; ++s)
{
// One-sample-per-pixel white noise: a mean-one exponential weight shared by RGB,
// like a single path sample's throughput.
float sample = -std::log(std::max(1e-6f, 1.0f - uniform(pixelId, frameIndex, s)));
if (hit.water && s == kIndirectSpecular)
sample = 1.0f + 0.35f * (sample - 1.0f); // a mirror's reflection ray is far less noisy
frame.clean[s][i * 3 + 0] = clean[s].x;
frame.clean[s][i * 3 + 1] = clean[s].y;
frame.clean[s][i * 3 + 2] = clean[s].z;
frame.noisy[s][i * 4 + 0] = to_half(clean[s].x * sample);
frame.noisy[s][i * 4 + 1] = to_half(clean[s].y * sample);
frame.noisy[s][i * 4 + 2] = to_half(clean[s].z * sample);
// A: ray hit distance for the indirect signals, any non-negative value for direct.
float alpha = 0.0f;
if (s == kIndirectDiffuse)
alpha = 0.5f + 3.0f * uniform(pixelId, frameIndex, 10 + s);
else if (s == kIndirectSpecular)
{
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
alpha = bounce.valid ? std::min(bounce.t, 100.0f) : 100.0f;
}
frame.noisy[s][i * 4 + 3] = to_half(alpha);
const V3 albedo = s == kDirectDiffuse || s == kIndirectDiffuse ? diffuse : specular;
combined = combined + mul(clean[s] * sample, albedo);
combinedClean = combinedClean + mul(clean[s], albedo);
}
frame.colorClean[i] = combinedClean;
frame.color[i * 4 + 0] = to_half(combined.x);
frame.color[i * 4 + 1] = to_half(combined.y);
frame.color[i * 4 + 2] = to_half(combined.z);
}
}
} // namespace fsr_scene