TL;DR: Add an experimental FSR Ray Regeneration backend on Windows, run the K
network natively on HIP with Vulkan->HIP shared memory, extend K kernels to
gfx11 (RDNA3), and add dependency/packaging/test tooling around them.
Windows: FSR RR backend, native HIP K runner, gfx11 K kernels, tooling
FSR Ray Regeneration (experimental)
- Add fsr_rr_backend.{h,cpp}, fsr_rr_passes.h and the fsr_rr/ prepare and
compose compute shaders. They run AMD's FidelityFX denoiser
(amd_fidelityfx_denoiser_dx12.dll, SDK 2.3, not bundled) on a private
vkd3d-proton device.
- Add the amdxc64 shim (tools/windows/amdxc64_shim) and two vkd3d-proton
patches for libcxx includes. AMD's amdxc64.dll is never loaded.
- d4r_nvngx_windows: RrFeature now selects between the native DLSS RR
backend and the FSR backend (D4R_RR_BACKEND=fsr). After the denoiser it
runs an internal Super Sampling stage (feature 1) to upscale. If SR is
unavailable and output != render size, the frame fails; it is never
returned noisy.
- d4r.ini / player_config: new [RayReconstruction] FsrDenoiser,
FsrStabilityBias (auto or >= 0) and FsrDetail (0-1), with validation, and
matching D4R_FSR_* environment variables.
- Add the FSR RR fixture, output-check and ngx test scripts, and a shader
compile script.
Native HIP K runner (Windows)
- CMake: add d4r_engine_k_hip (d4r-k-hip) plus separate volk/engine copies,
because VK_USE_PLATFORM_WIN32_KHR changes volk's entry points.
- Share Vulkan memory with HIP via Win32 opaque handles on Windows
(VK_KHR_external_memory_win32) and via fds on Linux. Handle types are now
intptr_t, and the platform choice is in D4R_EXT_MEMORY_* macros.
- HipNetwork: add debugRepeatHashes and debugNodeMicros, exposed as
D4R_HIP_REPEAT_CHECK, D4R_HIP_REPEAT_ALL and D4R_HIP_NODE_PROFILE in
replay.cpp.
- replay.cpp: display-resolution-motion frames (FrameFlagDisplayMotion) no
longer require near2rnd == out2rnd. Both values must still be in (0, 1].
K kernels on gfx11
- Allow position-token and enc1 tiling on __GFX11__ as well as gfx12.
- Add an opt-in ds_bpermute cross-half exchange
(D4R_WINDOWS_GFX11_DS_EXCHANGE, enabled for K on Windows gfx11). M keeps
its LDS path.
- Fix a block-barrier race in the position layer. Without POS_VSHARE a fast
wave could overwrite its hb rows while a slower wave still read them for a
later head, giving rare wrong results on gfx1101 (enc0 outputs differing
between identical launches).
Packaging and install
- install.bat, d4r.ini, WINDOWS_README: FSR option, and
install_dependencies.py to install pinned NVIDIA dependencies with staged,
hash-verified replacement and rollback.
- Update the release package, local package refresh and test scripts.
- Add the InteropBatchInputCopies option (default false). Batching is faster
on some synthetic runs, but player temporal exactness is unresolved.
Tooling and tests
- K model: capture_k_weights, build_k_model, compare_k_outputs,
benchmark_k_hip, build_capture_runtime, test_k_model and the
zluda-k-output-capture patch. Captured weights and models are private
local assets and must not enter release archives.
- Synthetic probes: test_m_ngx, test_sr_dynamic,
prepare_rr_dynamic_fixture and the d3d12/rr_ngx probe updates.
- Add tests/windows/test_install_dependencies.py.
Docs
- Update the windows-fsr-rr, k-performance, local-package, rr-interop and
rr-model-cache docs. Add windows-parity-handoff.md and
windows-parity-continuation-prompt.md.
This commit is contained in:
1 parent
247fe1092d
commit
a617c818de
62 files changed
+5894
-89
No files matched your search
@@ -0,0 +1,139 @@
|
||||
"""Synthetic noisy DLSS-RR-style scene with a known noise-free answer, in the rr_ngx_probe fixture format.
|
||||
|
||||
Left-handed view space, linear depth, render size == output size or output larger. Surfaces: a wall (stripes fixed in
|
||||
WORLD space), a disc (a sphere's normals) and a sky band. The colour is (diffuse + specular albedo) * smooth lighting
|
||||
times per-frame mean-preserving log-normal noise; the sky is noise-free. The camera may translate along world x
|
||||
(--camera-speed per frame): the guides then carry the exact motion vectors (previous minus current, render pixels) and
|
||||
the view matrix, and clean-<frame>.bin is the noise-free output-size image of that frame. clean.bin is frame 0's.
|
||||
--extents W1xH1,W2xH2,... makes the render size change per frame (cycling, each within --size): the guides are written
|
||||
at that frame's size and dynamic-extents.txt lists them (the probe's "dynamic-extents" mode).
|
||||
No game, no NVIDIA runtime. Output stays under ignored build/.
|
||||
"""
|
||||
import argparse
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
repo = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(repo / '.tools/python/vendor'))
|
||||
import numpy as np
|
||||
|
||||
FOV_DEGREES = 60.0
|
||||
|
||||
|
||||
def scene(width, height, cam_x=0.0, prev_cam_x=None):
|
||||
"""Returns depth, normal, diffuse, specular, clean colour, sky mask and motion (render pixels, prev - current)."""
|
||||
if prev_cam_x is None:
|
||||
prev_cam_x = cam_x
|
||||
y, x = np.mgrid[0:height, 0:width].astype('f8')
|
||||
u, v = (x + 0.5) / width * 2 - 1, 1 - (y + 0.5) / height * 2
|
||||
aspect = width / height
|
||||
tangent = math.tan(math.radians(FOV_DEGREES) / 2)
|
||||
# camera-space ray scale: x_view = u * aspect * tangent * z, y_view = v * tangent * z
|
||||
z = np.full((height, width), 10.0)
|
||||
normal = np.zeros((height, width, 3)); normal[..., 2] = -1.0
|
||||
diffuse = np.empty((height, width, 3)); diffuse[:] = (0.60, 0.50, 0.40)
|
||||
# the disc: a world-fixed sphere of radius 0.9 centred at world (0.9, -0.5, 7.5)
|
||||
centre = np.array([0.9, -0.5, 7.5]); radius = 0.9
|
||||
ray = np.stack([u * aspect * tangent, v * tangent, np.ones_like(u)], axis=-1)
|
||||
origin = np.array([cam_x, 0.0, 0.0])
|
||||
oc = origin - centre
|
||||
b = np.einsum('ijk,k->ij', ray, oc); a = np.einsum('ijk,ijk->ij', ray, ray); c = float(oc @ oc - radius * radius)
|
||||
disc_term = b * b - a * c
|
||||
hit = disc_term > 0
|
||||
t = np.where(hit, (-b - np.sqrt(np.maximum(disc_term, 0))) / a, 0)
|
||||
point = origin + ray * t[..., None]
|
||||
disc = hit & (t > 0)
|
||||
z[disc] = (t * ray[..., 2])[disc] # view z = t (the ray's z component is 1)
|
||||
normal[disc] = ((point - centre) / radius)[disc]
|
||||
world_x = cam_x + u * aspect * tangent * 10.0
|
||||
stripe = (np.floor(world_x / 0.45).astype(int) % 2) == 0
|
||||
diffuse[stripe & ~disc] = (0.35, 0.45, 0.55)
|
||||
diffuse[disc] = (0.80, 0.25, 0.20)
|
||||
sky = v > 0.7
|
||||
z[sky] = 1.0e8; normal[sky] = 0.0; diffuse[sky] = 0.0; disc = disc & ~sky
|
||||
specular = np.full((height, width, 3), 0.04); specular[sky] = 0.0
|
||||
# lighting is a function of the surface point (world x, screen-independent y), so it moves with the scene
|
||||
surf_x = np.where(disc, point[..., 0], world_x)
|
||||
light = (1.5 + 0.8 * np.sin(surf_x * 2.2) * np.cos(y * 360.0 / height / 41.0))[..., None] * np.array([1.0, 0.92, 0.8])
|
||||
clean = (diffuse + specular) * light
|
||||
clean[sky] = (0.35, 0.55, 0.95)
|
||||
# motion: previous minus current screen position of the same surface point, in render pixels
|
||||
motion = np.zeros((height, width, 2))
|
||||
dx = cam_x - prev_cam_x
|
||||
if dx != 0.0:
|
||||
depth = np.where(sky, 1.0, z)
|
||||
motion[..., 0] = dx / (depth * tangent * aspect) * (width / 2.0)
|
||||
motion[sky] = 0.0
|
||||
return z, normal, diffuse, specular, clean, sky, motion
|
||||
|
||||
|
||||
def half_bytes(a):
|
||||
return np.ascontiguousarray(a.astype('<f2')).tobytes()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', required=True, type=Path)
|
||||
parser.add_argument('--size', default='640x360', help='render size (the feature creation size)')
|
||||
parser.add_argument('--out-size', help='output size (default: render size)')
|
||||
parser.add_argument('--frames', type=int, default=24)
|
||||
parser.add_argument('--noise', type=float, default=0.6, help='log-normal sigma of the per-frame colour noise')
|
||||
parser.add_argument('--camera-speed', type=float, default=0.0, help='camera translation along world x per frame')
|
||||
parser.add_argument('--extents', help='per-frame render sizes WxH,WxH,... cycling (each within --size, aspect ratio kept)')
|
||||
parser.add_argument('--seed', type=int, default=7)
|
||||
args = parser.parse_args()
|
||||
if not args.output.resolve().is_relative_to((repo / 'build').resolve()):
|
||||
parser.error('output must stay under ignored build/')
|
||||
args.output.mkdir(parents=True, exist_ok=False)
|
||||
width, height = map(int, args.size.split('x'))
|
||||
out_width, out_height = map(int, (args.out_size or args.size).split('x'))
|
||||
extents = [tuple(map(int, item.split('x'))) for item in args.extents.split(',')] if args.extents else [(width, height)]
|
||||
for ew, eh in extents:
|
||||
if ew < 64 or eh < 64 or ew > width or eh > height:
|
||||
parser.error('every extent must be at least 64 and within --size')
|
||||
rng = np.random.default_rng(args.seed)
|
||||
f = 1.0 / math.tan(math.radians(FOV_DEGREES) / 2)
|
||||
near, far = 0.1, 1000.0
|
||||
lines = [f'size {width} {height} {out_width} {out_height}']
|
||||
extent_lines = []
|
||||
for i in range(args.frames):
|
||||
fw, fh = extents[i % len(extents)]
|
||||
extent_lines.append(f'{fw} {fh}')
|
||||
aspect = fw / fh
|
||||
proj = [f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, far / (far - near), 1, 0, 0, -near * far / (far - near), 0]
|
||||
cam = args.camera_speed * i
|
||||
z, normal, diffuse, specular, clean_render, sky, motion = scene(fw, fh, cam, cam - args.camera_speed if i else cam)
|
||||
if (out_width, out_height) == (fw, fh):
|
||||
clean_out, sky_out = clean_render, sky
|
||||
else:
|
||||
clean_out, sky_out = scene(out_width, out_height, cam)[4:6]
|
||||
noise = np.exp(args.noise * rng.standard_normal((fh, fw, 3)) - 0.5 * args.noise ** 2)
|
||||
noise[sky] = 1.0
|
||||
color = np.concatenate([clean_render * noise, np.ones((fh, fw, 1))], axis=-1)
|
||||
rgba = lambda c3: np.concatenate([c3, np.zeros((fh, fw, 1))], axis=-1)
|
||||
(args.output / f'{i}-color.bin').write_bytes(half_bytes(color))
|
||||
(args.output / f'{i}-motion.bin').write_bytes(half_bytes(motion))
|
||||
(args.output / f'{i}-depth.bin').write_bytes(np.ascontiguousarray(z.astype('<f4')).tobytes())
|
||||
(args.output / f'{i}-normals.bin').write_bytes(half_bytes(rgba(normal)))
|
||||
rough = np.zeros((fh, fw, 4)); rough[..., 0] = 0.5
|
||||
(args.output / f'{i}-roughness.bin').write_bytes(half_bytes(rough))
|
||||
alb = lambda a: np.concatenate([np.round(a * 255), np.full((fh, fw, 1), 255)], axis=-1).astype('u1').tobytes()
|
||||
(args.output / f'{i}-diffuse.bin').write_bytes(alb(diffuse))
|
||||
(args.output / f'{i}-specular.bin').write_bytes(alb(specular))
|
||||
view = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -cam, 0, 0, 1] # row-vector world -> view: translate by -camera
|
||||
lines.append('frame %d 0 0 1 1 1 %d 0 ' % (i, 1 if i == 0 else 0) + ' '.join(f'{v:.9g}' for v in view + proj))
|
||||
(args.output / f'clean-{i}.bin').write_bytes(half_bytes(np.concatenate([clean_out, np.ones((out_height, out_width, 1))], axis=-1)))
|
||||
if (out_width, out_height) != (fw, fh): # the best a render-resolution result could be, before upscaling
|
||||
(args.output / f'clean-render-{i}.bin').write_bytes(half_bytes(np.concatenate([clean_render, np.ones((fh, fw, 1))], axis=-1)))
|
||||
if i == 0:
|
||||
(args.output / 'clean.bin').write_bytes((args.output / 'clean-0.bin').read_bytes())
|
||||
np.save(args.output / 'sky.npy', sky_out)
|
||||
(args.output / 'frames.txt').write_text('\n'.join(lines) + '\n')
|
||||
if args.extents:
|
||||
(args.output / 'dynamic-extents.txt').write_text('\n'.join(extent_lines) + '\n')
|
||||
print('wrote', args.output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user