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:
1 parent
50fa65d87b
commit
db24805160
10 files changed
+133
-28
No files matched your search
@@ -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
@@ -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':
|
||||
|
||||
@@ -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}')
|
||||
|
||||
|
||||
Reference in new issue
Block a user