rr: Cyberpunk's extra inputs (SSS guide, colour before particles) and hardware depth

- GameFrameR/RFrameImages: DLSSD.ScreenSpaceSubsurfaceScatteringGuide (arg 264)
  and DLSSD.ColorBeforeParticles (output stage, texture 416 with its own history
  pair 384/392, zero-initialised on first use); guides array 6 -> 8
- DLSS.Use.HW.Depth: byte 980 and output byte 443 bit 24 (Cyberpunk passes the
  nonlinear depth buffer); replay's 'inverted' field becomes a bitmask
- shim: bind the two new resources, read the HW-depth flag, and accept every
  preset hint except E's 5 by mapping it to D's diamond_wallaby kernels
  (harness captures show 0x80000002/0x80000005 run preset D)
- harness: D4R_HARNESS_RR_EXTRA=particles,sss and D4R_HARNESS_RR_HW_DEPTH=1
- ptx2rr: out-of-range integer fetches return zero, matching ZLUDA's behaviour
  (the reference the stages are checked against)
- engine/rr/README: the capture findings behind the offsets
This commit is contained in:
woutermans committed 2026-10-12 13:06:59 +02:00
1 parent 50fa65d87b
commit db24805160
10 files changed
+133 -28

No files matched your search

+18
View File
@@ -171,3 +171,21 @@ exp2 / reciprocal (rrsim's exact arithmetic is further from the capture than the
single-use multiply-subtract pairs into fma, merging the two constant multiplications after the log transform,
`precise` on the inlined exp2, NoContraction on its conversions. The differing values come in spatial clusters, one
colour channel at a time, always one ulp, mostly just above 0.5 in surface 224.
## Cyberpunk 2077: more inputs and settings (2026-10-12)
Found on harness captures (`D4R_HARNESS_RR_EXTRA=particles,sss`, `D4R_HARNESS_RR_HW_DEPTH=1`, `D4R_RR_PRESET=N`):
| what the game passes | where NGX puts it |
|---|---|
| `DLSSD.ScreenSpaceSubsurfaceScatteringGuide` (R16F) | encoder 0 texture 264 (its handle is the only switch) |
| `DLSSD.ColorBeforeParticles` (RGBA16F) | output stage texture 416, flag byte 432, and a second output-size history: surface 384, read back next frame as texture 392 (linear) |
| `DLSS.Use.HW.Depth = 1` | encoder 0 byte 980, output stage byte 443 |
| preset hint | 5 runs preset E; every other value (0-4, 6, 0x80000002, 0x80000005) runs preset D |
Integer texture fetches outside the image return zero (`fetch0`), which is what the reference does; CUDA would clamp.
With Cyberpunk's set (hit distance, particles, subsurface guide, hardware depth) the engine's presented image is
byte-identical to the CUDA path through the shim on the frames the harness can compare (640x360 -> 1920x1080), and
with RE Requiem's set the network is identical on every frame (output 107-109 dB). At 1280x720 and 1920x1080 -> 4K
without these inputs the output still falls to 43-53 dB after the first frame (the output stage's one-ulp values);
a correctly rounded log2 there matches the capture worse than the GPU instruction does.
+6 -4
View File
@@ -66,6 +66,7 @@ uint HU2_(u16vec2 u) { return uint(u.x) | (uint(u.y) << 16); }
uint H2U(f16vec2 h) { return HU2_(float16BitsToUint16(h)); }
int s16(uint u) { return (int(u) << 16) >> 16; }
float rcp_a(float x) { return 1.0 / x; }
vec4 fetch0(sampler2D t, ivec2 c) { const ivec2 n = textureSize(t, 0); return (c.x < 0 || c.y < 0 || c.x >= n.x || c.y >= n.y) ? vec4(0.0) : texelFetch(t, c, 0); }
// x + copysign(0.5, x) rounded toward zero and then truncated: round half away from zero, exactly
float round_half_away(float x) { float t = trunc(x); return abs(x - t) >= 0.5 ? t + sign(x) : t; }
uint PARAM32(uint o) { return param.w[o >> 2]; }
@@ -509,10 +510,11 @@ class RRT(base.T):
off = self.handle_off(h)
self.textures.add(off)
if 'base' in o and o[-1] == 's32':
# integer coordinates still go through the texture's address mode in CUDA (clamp for every denoiser
# texture); texelFetch does not clamp, and what it returns out of range depends on the device's robustness
fetch = (f'texelFetch(T{off}, clamp(ivec2({self.val(c[0], "s32")}, {self.val(c[1], "s32")}), ivec2(0), '
f'textureSize(T{off}, 0) - 1), 0)')
# an integer fetch outside the image: CUDA applies the texture's address mode (clamp for every denoiser
# texture), but the reference this engine is checked against is ZLUDA, whose image load returns zero there
# (zluda_ptx_impl.cpp fetch_2D). The output stage's specular reprojection reads such neighbours at the image
# border: with clamp its surfaces were 99.92% identical to the capture, with zero 99.997% (capDX, 2026-10-12)
fetch = f'fetch0(T{off}, ivec2({self.val(c[0], "s32")}, {self.val(c[1], "s32")}))'
elif 'base' in o and o[-1] == 'f32':
fetch = f'textureLod(T{off}, vec2({self.val(c[0], "f32")}, {self.val(c[1], "f32")}), 0.0)'
elif 'level' in o and o[-1] == 'f32':
+7 -2
View File
@@ -20,7 +20,8 @@ import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from rr_capture import Capture # noqa: E402
OPTIONAL = {'hitdistance': 248, 'disocclusion': 296}
OPTIONAL = {'hitdistance': 248, 'disocclusion': 296, 'sss': 264} # encoder 0 textures
OPTIONAL_OUTPUT = {'particles': 416} # output stage textures
INPUTS = {'color': 160, 'motion': 168, 'normals': 184, 'roughness': 192, 'diffuse': 232, 'specular': 240, 'depth': 512}
@@ -49,6 +50,10 @@ def main():
for name, off in OPTIONAL.items(): # DLSSD.SpecularHitDistance, DLSS.DisocclusionMask when the game passed them
if off in images:
(o.out / f'{f}-{name}.bin').write_bytes(images[off].path.read_bytes())
out_images = {i.offset: i for i in cap.find('hkpn_output', f).images}
for name, off in OPTIONAL_OUTPUT.items(): # DLSSD.ColorBeforeParticles
if off in out_images:
(o.out / f'{f}-{name}.bin').write_bytes(out_images[off].path.read_bytes())
clip = mat(660)
view = mat(596) @ np.linalg.inv(clip)
view[3, :3] = 0.0
@@ -56,7 +61,7 @@ def main():
mvx, mvy = f32(344) * rw / ow, f32(348) * rh / oh
reset = 1 if f == 1 or not o.no_reset else 0
values = ' '.join(f'{v:.9g}' for v in list(view.flatten()) + list(clip.flatten()))
lines.append(f'frame {f} {jx:.9g} {jy:.9g} {mvx:.9g} {mvy:.9g} 1 {reset} {u32(528)} {values}')
lines.append(f'frame {f} {jx:.9g} {jy:.9g} {mvx:.9g} {mvy:.9g} 1 {reset} {u32(528) | (a[980] << 1)} {values}')
(o.out / 'frames.txt').write_text('\n'.join(lines) + '\n')
print(f'{o.frames} frames of {o.capture} in {o.out}')