engine: SR reconstruction samples the history once when both candidates have one vector

post_m.comp (presets M and L) blends the history along the two most likely of
three motion candidates and sampled it once per candidate (five bilinear
taps; L also expands each sample to linear light). Where the motion is
uniform the two candidates usually have the same vector: the same position,
so the same bicubic sample or the same fallback colour (a NaN vector never
compares equal, +0 and -0 give the same position). That case now takes the
first candidate's value.

Every output frame byte-identical (M and L, HDR and LDR; 1280x720 -> 1440p,
1114x626 -> 1080p for the compact tile tier, 1920x1080 -> 1440p for the 15x15
tier, the stress inputs), and so is everything D4R_M_DUMP writes, the
history, half history and packed image included. Taking 0.99 times the first
sample there instead changes every frame of every input, the reset frames
too. Only post_m*.spv / post_l*.spv change.

RX 9070 XT, d4r-m 720p -> 1440p, interleaved, reconstruction stage:
uniform motion 325 -> 288 us (L 401 -> 354), compact tier at 1114x626 ->
1080p 219 -> 193 (L 275 -> 244); random motion unchanged (377 -> 374,
L 404 -> 401). Frame 1570 -> 1541 us (L 2299 -> 2249) on the uniform-motion
frames.

Signed-off-by: itchyfeetleech <[email protected]>
This commit is contained in:
itchyfeetleech committed 2026-10-19 21:02:50 +13:00
1 parent 9c9145ac02
commit 8c0635242b
1 file changed
+9 -2
+9 -2
View File
@@ -214,6 +214,9 @@ void main()
// the fallback colour is only fetched where the history sample leaves the image or the history was reset: // the fallback colour is only fetched where the history sample leaves the image or the history was reset:
// the value is the same either way, so computing it in the branch saves the fetch on ordinary pixels // the value is the same either way, so computing it in the branch saves the fetch on ordinary pixels
const bool out1 = q1.x < 0.0 || q1.y < 0.0 || q1.x > size.x || q1.y > size.y || p.reset != 0u; const bool out1 = q1.x < 0.0 || q1.y < 0.0 || q1.x > size.x || q1.y > size.y || p.reset != 0u;
// the second candidate often has the first one's vector (where the motion is uniform): the same position,
// so the first one's sample or fallback (a NaN is never equal, and +0 and -0 give the same position)
const bool same = all(equal(second.xy, big.xy));
#if PRESET_L #if PRESET_L
// linear light: a sample's expansion (when the previous exposure is positive) or the colour texel // linear light: a sample's expansion (when the previous exposure is positive) or the colour texel
const vec3 h1 = out1 ? texelFetch(colorTex, colourTexel(near), 0).rgb : historyLinear(q1, p.expoPrev == 0.0 ? 1.0 : p.expoPrev); const vec3 h1 = out1 ? texelFetch(colorTex, colourTexel(near), 0).rgb : historyLinear(q1, p.expoPrev == 0.0 ? 1.0 : p.expoPrev);
@@ -221,7 +224,9 @@ void main()
if (b2 > 0.0) if (b2 > 0.0)
{ {
const vec2 q2 = centre + second.xy; const vec2 q2 = centre + second.xy;
if (q2.x < 0.0 || q2.y < 0.0 || q2.x > size.x || q2.y > size.y || p.reset != 0u) if (same)
h2 = h1;
else if (q2.x < 0.0 || q2.y < 0.0 || q2.x > size.x || q2.y > size.y || p.reset != 0u)
h2 = out1 ? h1 : texelFetch(colorTex, colourTexel(near), 0).rgb; h2 = out1 ? h1 : texelFetch(colorTex, colourTexel(near), 0).rgb;
else h2 = historyLinear(q2, p.expoPrev == 0.0 ? 1.0 : p.expoPrev); else h2 = historyLinear(q2, p.expoPrev == 0.0 ? 1.0 : p.expoPrev);
} }
@@ -232,7 +237,9 @@ void main()
if (b2 > 0.0) if (b2 > 0.0)
{ {
const vec2 q2 = centre + second.xy; const vec2 q2 = centre + second.xy;
if (q2.x < 0.0 || q2.y < 0.0 || q2.x > size.x || q2.y > size.y || p.reset != 0u) if (same)
h2 = h1;
else if (q2.x < 0.0 || q2.y < 0.0 || q2.x > size.x || q2.y > size.y || p.reset != 0u)
h2 = out1 ? h1 : compress(e2 * texelFetch(colorTex, colourTexel(near), 0).rgb); h2 = out1 ? h1 : compress(e2 * texelFetch(colorTex, colourTexel(near), 0).rgb);
else h2 = history(q2); else h2 = history(q2);
} }