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:
1 parent
3345fffcd3
commit
247fe1092d
60 files changed
+7168
-66
No files matched your search
@@ -0,0 +1,140 @@
|
||||
"""Compare K input batching using identical synthetic frames and alternating runs.
|
||||
|
||||
Uses an existing private runtime; never installs into a game. CPU interop timing
|
||||
includes synchronization and NGX execution, and is not a GPU network-only time.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
for key in ('probe', 'shim', 'runtime', 'core', 'dlss', 'output'):
|
||||
parser.add_argument('--' + key, type=lambda s: Path(s).resolve(), required=True)
|
||||
parser.add_argument('--resolution', default='1920x1080')
|
||||
parser.add_argument('--input-resolution', help='defaults to nominal Quality (two-thirds per axis)')
|
||||
parser.add_argument('--frames', type=int, default=16)
|
||||
parser.add_argument('--profile-stages', action='store_true',
|
||||
help='diagnostic CPU stage logging; adds overhead to timings')
|
||||
parser.add_argument('--candidate-native', type=lambda s: Path(s).resolve(),
|
||||
help='compare an experimental native directory instead of input batching')
|
||||
parser.add_argument('--profile-kernels', action='store_true',
|
||||
help='diagnostic deferred GPU events, allowing legacy-stream dependencies; timings are instrumented')
|
||||
args = parser.parse_args()
|
||||
if args.frames < 8 or args.frames > 120:
|
||||
parser.error('frames must be 8..120')
|
||||
if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.resolution):
|
||||
parser.error('resolution must be WIDTHxHEIGHT')
|
||||
ow, oh = map(int, args.resolution.split('x'))
|
||||
if not args.input_resolution:
|
||||
args.input_resolution = f'{ow * 2 // 3}x{oh * 2 // 3}'
|
||||
if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.input_resolution):
|
||||
parser.error('input-resolution must be WIDTHxHEIGHT')
|
||||
build_root = Path(__file__).resolve().parents[2] / 'build'
|
||||
if not args.output.is_relative_to(build_root.resolve()):
|
||||
parser.error('output must stay under the repository ignored build directory')
|
||||
args.output.mkdir(parents=True, exist_ok=False)
|
||||
report = {'passed': False, 'resolution': args.resolution, 'warmupFrames': 4,
|
||||
'inputResolution': args.input_resolution,
|
||||
'stageProfiling': args.profile_stages,
|
||||
'kernelProfiling': args.profile_kernels,
|
||||
'comparison': 'native kernels' if args.candidate_native else 'input batching',
|
||||
'timing': 'CPU interop including NGX and synchronization', 'runs': []}
|
||||
runtime = args.runtime
|
||||
native = runtime / 'native'
|
||||
if (native / 'gfx1101').is_dir():
|
||||
native = native / 'gfx1101'
|
||||
nvapi = runtime / 'nvapi/nvapi64.dll'
|
||||
if not nvapi.is_file():
|
||||
nvapi = runtime / 'nvapi/gfx1101/nvapi64.dll'
|
||||
report['artifacts'] = {str(p): hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
for p in (args.probe, args.shim, args.core, args.dlss)}
|
||||
report['nativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
for p in sorted(native.glob('*')) if p.is_file()}
|
||||
if args.candidate_native:
|
||||
report['candidateNativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
for p in sorted(args.candidate_native.glob('*')) if p.is_file()}
|
||||
env = os.environ.copy()
|
||||
# Keep profiling and arithmetic settings independent of the caller's shell.
|
||||
for key in list(env):
|
||||
if key.startswith('D4R_') or key in ('ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR', 'ZLUDA_CUDA_LIB'):
|
||||
del env[key]
|
||||
env.update(HIP_PATH=str(runtime / 'hip'),
|
||||
ZLUDA_CUDA_LIB=str(runtime / 'zluda/nvcuda.dll'),
|
||||
D4R_NVAPI_BACKEND=str(runtime / 'nvapi/backend/nvapi64.dll'),
|
||||
D4R_FORMAT_MODULE=str(runtime / 'pixel_convert_gfx1101.hsaco'),
|
||||
D4R_ZLUDA_NATIVE_DIR=str(native),
|
||||
D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_FP8='1',
|
||||
D4R_ZLUDA_WMMA_F16_REFERENCE='1', D4R_QUIET_API='1',
|
||||
ZLUDA_CACHE_DIR=str(Path(os.environ['LOCALAPPDATA']) / 'd4r/cache'),
|
||||
D4R_NGX_DATA_DIR=str(args.output / 'ngx-data'))
|
||||
env['PATH'] = str(runtime / 'hip/bin') + os.pathsep + str(runtime / 'zluda') + os.pathsep + env['PATH']
|
||||
if args.profile_stages:
|
||||
env['D4R_PROFILE_STAGES'] = '1'
|
||||
if args.profile_kernels:
|
||||
env.update(D4R_ZLUDA_PROFILE_DEFERRED='1', D4R_ZLUDA_PROFILE_ALLOW_LEGACY='1',
|
||||
D4R_ZLUDA_PROFILE_EVERY='1')
|
||||
try:
|
||||
reference = None
|
||||
for index, batched in enumerate((False, True, True, False)):
|
||||
label = ('native-candidate' if args.candidate_native else 'batch') if batched else 'control'
|
||||
output = args.output / f'{index}-{label}'
|
||||
output.mkdir()
|
||||
env['D4R_DIAG_DIR'] = str(output)
|
||||
if batched and not args.candidate_native:
|
||||
env['D4R_BATCH_INPUT_COPIES'] = '1'
|
||||
else:
|
||||
env.pop('D4R_BATCH_INPUT_COPIES', None)
|
||||
env['D4R_ZLUDA_NATIVE_DIR'] = str(args.candidate_native if batched and args.candidate_native else native)
|
||||
command = [str(args.probe), '--module', str(args.shim), '--hip-root', str(runtime / 'hip'),
|
||||
'--cuda-dll', str(runtime / 'zluda/nvcuda.dll'), '--ngx-core', str(args.core),
|
||||
'--dlss-dll', str(args.dlss), '--nvapi-dll', str(nvapi),
|
||||
'--ngx-abi', 'project', '--interop-mode', 'command-list', '--preset', '11',
|
||||
'--ngx-output-resolution', args.resolution,
|
||||
'--ngx-input-resolution', args.input_resolution, '--iterations', str(args.frames)]
|
||||
with (output / 'stdout.log').open('wb') as out, (output / 'stderr.log').open('wb') as err:
|
||||
result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=180)
|
||||
text = (output / 'stdout.log').read_text(errors='replace')
|
||||
if result.returncode or 'PASS D3D12_DLSS' not in text:
|
||||
raise RuntimeError(f'run {index} failed ({result.returncode}); inspect {output}')
|
||||
iw, ih = map(int, args.input_resolution.split('x'))
|
||||
expected_size = f'D3D12_NGX_DIMENSIONS input={iw}x{ih} output={ow}x{oh}'
|
||||
if expected_size not in text or f'D4R_INTEROP async=0 batch_inputs={int(batched and not args.candidate_native)}' not in text:
|
||||
raise RuntimeError(f'run {index}: requested dimensions/interop mode not applied')
|
||||
if len(re.findall(r'D3D12_OUTPUT preset=11 frame=\d+ finite=1 ', text)) != args.frames:
|
||||
raise RuntimeError(f'run {index}: missing finite-output checks')
|
||||
times = [float(x) for x in re.findall(r'D4R_FRAME .*?interop_ngx_ms=([0-9.]+)', text)]
|
||||
if len(times) != args.frames:
|
||||
raise RuntimeError(f'run {index}: incomplete frame timing')
|
||||
hashes = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in output.glob('output-*.rgba16f')}
|
||||
if len(hashes) != args.frames:
|
||||
raise RuntimeError(f'run {index}: incomplete output readback')
|
||||
if reference is None:
|
||||
reference = hashes
|
||||
elif hashes != reference:
|
||||
raise RuntimeError(f'run {index}: output differs from control')
|
||||
report['runs'].append({'batch': batched and not bool(args.candidate_native),
|
||||
'candidate': batched, 'allFrameMs': times,
|
||||
'medianMs': statistics.median(times[4:]), 'outputHashes': hashes})
|
||||
print(f'run={index} variant={label} warmed_cpu_interop_ms={statistics.median(times[4:]):.3f}', flush=True)
|
||||
for mode in (False, True):
|
||||
values = [t for row in report['runs'] if row['candidate'] == mode for t in row['allFrameMs'][4:]]
|
||||
key = ('candidateMedianMs' if args.candidate_native else 'batchMedianMs') if mode else 'controlMedianMs'
|
||||
report[key] = statistics.median(values)
|
||||
report['passed'] = True
|
||||
except Exception as error:
|
||||
report['error'] = str(error)
|
||||
print(str(error), flush=True)
|
||||
finally:
|
||||
(args.output / 'summary.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
return 0 if report['passed'] else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
@@ -14,6 +14,7 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from gpu_targets import TARGETS
|
||||
from llvm22_ir import compatible_ir
|
||||
|
||||
|
||||
def require_code_object_target(path, target):
|
||||
@@ -39,6 +40,8 @@ def main():
|
||||
parser.error('this private experimental build recipe supports K v2 input/output variants only')
|
||||
repo = pathlib.Path(__file__).resolve().parents[2]
|
||||
output = args.output_directory.resolve()
|
||||
if not output.is_relative_to((repo / 'build').resolve()):
|
||||
parser.error('private texture outputs must stay under the repository ignored build directory')
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
for existing in output.glob('*.hsaco'):
|
||||
require_code_object_target(existing, args.gpu_arch)
|
||||
@@ -46,8 +49,16 @@ def main():
|
||||
work.mkdir(exist_ok=True)
|
||||
hip = args.hip_root.resolve()
|
||||
zluda = args.zluda_root.resolve()
|
||||
tools = {name: hip / 'bin' / (name + '.exe') for name in ('clang++', 'llvm-dis', 'llvm-as')}
|
||||
tools = {}
|
||||
for name in ('clang++', 'llvm-dis', 'llvm-as'):
|
||||
candidates = [hip / folder / (name + '.exe') for folder in ('lib/llvm/bin', 'bin')]
|
||||
tools[name] = next((path for path in candidates if path.is_file()), candidates[0])
|
||||
emitter = zluda / 'd4r_emit.exe'
|
||||
# The emitter's LLVM reader must consume the helper bitcode. A newer HIP
|
||||
# compiler can emit textual IR, but its bitcode attributes may be unreadable.
|
||||
emitter_assembler = zluda / 'llvm-as.exe'
|
||||
if emitter_assembler.is_file():
|
||||
tools['llvm-as'] = emitter_assembler
|
||||
for path in [args.dlss_dll, emitter, *tools.values()]:
|
||||
if not path.is_file():
|
||||
parser.error(f'missing dependency: {path}')
|
||||
@@ -63,7 +74,8 @@ def main():
|
||||
|
||||
def run(label, command):
|
||||
with (work / (label + '.log')).open('wb') as log:
|
||||
result = subprocess.run([str(arg) for arg in command], env=env, stdout=log, stderr=subprocess.STDOUT)
|
||||
result = subprocess.run([str(arg) for arg in command], env=env, stdout=log,
|
||||
stderr=subprocess.STDOUT, timeout=600)
|
||||
if result.returncode:
|
||||
print((work / (label + '.log')).read_text(errors='replace')[-8000:], file=sys.stderr)
|
||||
raise RuntimeError(f'{label} failed ({result.returncode}); see {work}')
|
||||
@@ -89,11 +101,16 @@ def main():
|
||||
run('disassemble', [tools['llvm-dis'], raw, '-o', ir])
|
||||
lines = []
|
||||
for line in ir.read_text().splitlines():
|
||||
# Helpers are linked directly into the translated module. Their ThinLTO
|
||||
# index becomes stale when the unused HIP/LLVM globals below are removed.
|
||||
if re.match(r'^\^\d+\s*=', line):
|
||||
continue
|
||||
if re.search(r'@llvm.used|wchar_size|llvm.module.flags|__hip_cuid', line):
|
||||
continue
|
||||
line = line.replace('optnone', '').replace(f'"target-cpu"="{args.gpu_arch}"', '')
|
||||
lines.append(re.sub(r'"target-features"="[^"]+"', '', line))
|
||||
ir.write_text('\n'.join(lines) + '\n')
|
||||
ir.write_text(compatible_ir('\n'.join(lines) + '\n') if emitter_assembler.is_file()
|
||||
else '\n'.join(lines) + '\n')
|
||||
run('assemble', [tools['llvm-as'], ir, '-o', extra])
|
||||
env['D4R_ZLUDA_EXTRA_BC'] = str(extra)
|
||||
run('emit', [emitter, edited, work / 'emitted', args.gpu_arch])
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
"""Build exact gfx11 RR D/E models natively on Windows, without Git Bash.
|
||||
|
||||
Derived weights/shaders remain under ignored build/. Nothing is installed into
|
||||
a game or bundled. Uses the same packers and compiler recipe as the Linux shell.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def sha(path):
|
||||
with path.open('rb') as stream:
|
||||
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
repo = Path(__file__).resolve().parents[2]
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--dll', required=True, type=lambda p: Path(p).resolve())
|
||||
parser.add_argument('--preset', choices=('D', 'E'), default='D')
|
||||
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve())
|
||||
parser.add_argument('--glslang', type=lambda p: Path(p).resolve(), default=repo / '.tools/glslang-16.6.0/bin/glslangValidator.exe')
|
||||
parser.add_argument('--reuse', action='store_true')
|
||||
args = parser.parse_args()
|
||||
if not args.output.is_relative_to((repo / 'build').resolve()):
|
||||
parser.error('derived models must stay under ignored build/')
|
||||
if sha(args.dll) != '4bc7ea5fcb2f32cf86bc2cb072e8d2860914a0be511cbdcb2fc206c1d9d83b80':
|
||||
parser.error('requires pinned NVIDIA RR 310.9.1 DLL')
|
||||
if sha(args.glslang) != '9643709c02c83e26773f96b6a87f197ab2e84f78be0879ad8fb3619418b5d9ad':
|
||||
parser.error('requires pinned glslang 16.6.0 executable')
|
||||
env = os.environ.copy()
|
||||
for key in tuple(env):
|
||||
if key.startswith('D4R_RR_'):
|
||||
del env[key]
|
||||
env.update(D4R_RR_GFX11='1', D4R_RR_RELAXED='0', PYTHONPATH=str(repo / '.tools/python/vendor'))
|
||||
env['PATH'] = str(args.glslang.parent) + os.pathsep + env['PATH']
|
||||
numpy_version = subprocess.check_output([sys.executable, '-c', 'import numpy; print(numpy.__version__)'], env=env, text=True, timeout=30).strip()
|
||||
sources = {str(Path(__file__).relative_to(repo)).replace('\\', '/'): sha(Path(__file__))}
|
||||
for directory in ('engine', 'vulkan', 'kernels/tools'):
|
||||
for path in sorted((repo / directory).rglob('*')):
|
||||
if path.is_file() and path.suffix in ('.py', '.comp', '.glsl', '.h', '.inc', '.sh', '.txt'):
|
||||
sources[path.relative_to(repo).as_posix()] = sha(path)
|
||||
identity = {'schema': 1, 'preset': args.preset, 'architecture': 'gfx1101', 'arithmetic': 'exact-gfx11',
|
||||
'dll': sha(args.dll), 'glslang': sha(args.glslang), 'python': sha(Path(sys.executable)),
|
||||
'numpy': numpy_version, 'sources': sources}
|
||||
stamp = args.output / 'windows-native-python-cache.json'
|
||||
def products():
|
||||
return {p.name: sha(p) for p in sorted(args.output.iterdir()) if p.is_file() and p.name != stamp.name}
|
||||
if args.output.exists():
|
||||
if not args.reuse:
|
||||
parser.error('use a fresh output directory or --reuse')
|
||||
cached = json.loads(stamp.read_text())
|
||||
if cached['identity'] != identity or cached['products'] != products():
|
||||
raise RuntimeError('RR cache changed; use a fresh output directory')
|
||||
print('RR_NATIVE_CACHE_HIT preset=' + args.preset)
|
||||
return
|
||||
args.output.mkdir(parents=True)
|
||||
out = args.output
|
||||
work = out / 'native-work'
|
||||
work.mkdir()
|
||||
rr = repo / 'engine/rr'
|
||||
serial = 0
|
||||
def run(command, label):
|
||||
nonlocal serial
|
||||
serial += 1
|
||||
print(f'RR_BUILD step={serial} {label}', flush=True)
|
||||
log = work / f'{serial:02}-{label}.log'
|
||||
with log.open('wb') as stream:
|
||||
process = subprocess.Popen([str(p) for p in command], cwd=repo, env=env, stdout=stream, stderr=subprocess.STDOUT)
|
||||
try:
|
||||
code = process.wait(timeout=600)
|
||||
except subprocess.TimeoutExpired:
|
||||
if os.name == 'nt':
|
||||
subprocess.run(['taskkill', '/PID', str(process.pid), '/T', '/F'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30)
|
||||
else:
|
||||
process.kill()
|
||||
process.wait(timeout=30)
|
||||
raise RuntimeError('RR build timed out; see ' + str(log))
|
||||
if code:
|
||||
raise RuntimeError(f'RR build failed ({code}); see {log}')
|
||||
return log.read_text(errors='replace').strip()
|
||||
def python(script, *arguments, label):
|
||||
return run([sys.executable, script, *arguments], label)
|
||||
python(repo / 'kernels/tools/extract_dlss_ptx.py', args.dll, work / 'ptx', label='extract')
|
||||
python(repo / 'engine/compile_rr.py', '--dll', args.dll, '--preset', args.preset, out,
|
||||
'--ptx-dir', work / 'ptx', '--network-bindings-only', label='bindings')
|
||||
python(rr / 'dll_weights.py', args.dll, work / 'dll', '--model', '0' if args.preset == 'D' else '1', label='weights')
|
||||
manifest = (out / 'rr.txt').read_text().replace(f'preset {4 if args.preset == "D" else 5}\n',
|
||||
f'preset {4 if args.preset == "D" else 5}\narithmetic gfx11\n')
|
||||
defines = ['-DRR_EXACT_CONVERT', '-DRR_GFX11'] + (['-DRR_PRESET_D'] if args.preset == 'D' else [])
|
||||
def native(stage, shader, include):
|
||||
nonlocal manifest
|
||||
weights = out / f'weights-{stage}m.bin'
|
||||
wide = int(python(rr / 'widen_weights.py', weights, label='widen-' + stage).splitlines()[-1])
|
||||
run([args.glslang, '--target-env', 'vulkan1.3', '-V', *defines, f'-DW_WIDE={wide}u',
|
||||
'-I' + str(include), '-I' + str(rr), '-o', out / f'{stage}_m.spv', shader], 'compile-' + stage)
|
||||
python(rr / 'spv_exact_convert.py', out / f'{stage}_m.spv', label='exact-' + stage)
|
||||
pattern = rf'^stage {stage} {stage}\.spv 32 weights-{stage}\.bin (.*)$'
|
||||
def replacement(match):
|
||||
tail = match[1].removesuffix(' noopt')
|
||||
return f'stage {stage} {stage}_m.spv 32 weights-{stage}m.bin {tail}'
|
||||
manifest, count = re.subn(pattern, replacement, manifest, flags=re.MULTILINE)
|
||||
if count != 1:
|
||||
raise RuntimeError('Missing/duplicate native stage: ' + stage)
|
||||
for stage in ('enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1'):
|
||||
directory = work / stage
|
||||
directory.mkdir()
|
||||
shutil.copyfile(work / 'dll' / f'{stage}.npz', directory / 'weights.npz')
|
||||
python(rr / 'swin_pack.py', stage, directory / 'weights.npz', out / f'weights-{stage}m.bin', directory / 'swin_stage.h', label='pack-' + stage)
|
||||
native(stage, rr / 'swin_m.comp', directory)
|
||||
for stage in ('dec0', 'enc0'):
|
||||
directory = work / stage
|
||||
directory.mkdir()
|
||||
shutil.copyfile(work / 'dll' / f'{stage}.npz', directory / 'weights.npz')
|
||||
if stage == 'dec0':
|
||||
python(rr / 'dec0_pack.py', directory / 'weights.npz', out / 'weights-dec0m.bin', directory / 'dec0_m_w.h', label='pack-dec0')
|
||||
native(stage, rr / 'dec0_m.comp', directory)
|
||||
else:
|
||||
base = 0 if args.preset == 'D' else 4096
|
||||
python(rr / 'enc0_pack.py', directory / 'weights.npz', directory / 'enc0_m.bin', directory / 'enc0_m_w.h', str(base), label='pack-enc0')
|
||||
noise = b'' if not base else (work / 'dll/noise.bin').read_bytes()
|
||||
if len(noise) != base:
|
||||
raise RuntimeError('Invalid E noise table')
|
||||
(out / 'weights-enc0m.bin').write_bytes(noise + (directory / 'enc0_m.bin').read_bytes())
|
||||
python(rr / 'gen_enc0_m.py', out / 'enc0.generated.comp', directory / 'enc0_m.comp', label='gather-enc0')
|
||||
native(stage, directory / 'enc0_m.comp', directory)
|
||||
metadata = json.loads((out / 'model.json').read_text())
|
||||
if metadata['stages']['hkpn'].get('shader') == 'generated from the DLL':
|
||||
manifest = manifest.replace('stage hkpn hkpn.spv 32 ', 'stage hkpn hkpn.spv 64 ')
|
||||
for stage in ('enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0'):
|
||||
path = out / f'weights-{stage}.bin'
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
(out / 'rr.txt').write_text(manifest, newline='\n')
|
||||
stages = [line.split() for line in manifest.splitlines() if line.startswith('stage ')]
|
||||
if len(stages) != 12 or 'arithmetic gfx11' not in manifest:
|
||||
raise RuntimeError('Incomplete native manifest')
|
||||
for fields in stages:
|
||||
if not (out / fields[2]).is_file() or not (out / fields[4]).is_file():
|
||||
raise RuntimeError('Missing stage product')
|
||||
textures = [] if fields[5] == '-' else list(map(int, fields[5].split(',')))
|
||||
surfaces = [] if fields[6] == '-' else [[int(s.split(':')[0]), s.split(':')[1]] for s in fields[6].split(',')]
|
||||
(out / fields[2]).with_suffix('.json').write_text(json.dumps({'textures': textures, 'surfaces': surfaces}) + '\n')
|
||||
stamp.write_text(json.dumps({'identity': identity, 'products': products()}, indent=2) + '\n')
|
||||
print('RR_NATIVE_MODEL_BUILT preset=' + args.preset + ' output=' + str(out))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -29,7 +29,7 @@ int main(int argc, char** argv) {
|
||||
for (const auto& pair : {std::make_pair("D4R_HIP_ROOT", args.hip_root),
|
||||
std::make_pair("D4R_NVCUDA_DLL", args.cuda_dll), std::make_pair("D4R_NVAPI_DLL", args.nvapi_dll),
|
||||
std::make_pair("D4R_NGX_CORE", args.ngx_core), std::make_pair("D4R_DLSS_DLL", args.dlss_dll)})
|
||||
if (_putenv_s(pair.first, pair.second.c_str())) throw std::runtime_error("Cannot set runtime environment");
|
||||
if (_putenv_s(pair.first, args.player_mode ? "" : pair.second.c_str())) throw std::runtime_error("Cannot set runtime environment");
|
||||
HipApi hip(args.hip_root); hipDeviceProp_t props{}; hip.select_architecture(args.device, props);
|
||||
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "CreateDXGIFactory1");
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Native Windows backend. The Linux/Wine implementation remains separate.
|
||||
#include "ngx_windows_runtime.h"
|
||||
#include "ngx_runtime_initialization.h"
|
||||
#include "player_config.h"
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
#include "rr_native_backend.h"
|
||||
#endif
|
||||
#include <dxgi.h>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
@@ -8,9 +12,102 @@
|
||||
namespace {
|
||||
std::mutex apiMutex;
|
||||
std::shared_ptr<d4r::win::Runtime> runtime;
|
||||
std::shared_ptr<d4r::win::RuntimeInitialization> initialization;
|
||||
bool deferredSr = false;
|
||||
void ensure_sr_runtime() {
|
||||
if (!runtime) {
|
||||
if (!initialization) throw std::runtime_error("NGX is not initialized");
|
||||
runtime = initialization->create();
|
||||
}
|
||||
}
|
||||
void initialize(ID3D12Device* device, unsigned long long app, const wchar_t* data, unsigned sdk,
|
||||
const d4r::ngx::FeatureCommonInfo* common, const d4r::ngx::ProjectIdentity* project = nullptr) {
|
||||
if (initialization) {
|
||||
if (initialization->device.Get() != device) throw std::runtime_error("NGX already initialized on another device");
|
||||
return;
|
||||
}
|
||||
auto candidate = std::make_shared<d4r::win::RuntimeInitialization>(device, app, data, sdk, common, project);
|
||||
bool native = false;
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
const char* backend = std::getenv("D4R_RR_BACKEND"); native = backend && std::string(backend) == "native";
|
||||
#endif
|
||||
// Default SR startup remains eager. Native RR owns its Vulkan execution
|
||||
// independently; SR initializes only when its capabilities/feature are used.
|
||||
if (!native) runtime = candidate->create();
|
||||
initialization = std::move(candidate); deferredSr = native;
|
||||
}
|
||||
struct Handle { unsigned Id; };
|
||||
unsigned nextId = 1;
|
||||
std::unordered_map<Handle*, std::shared_ptr<d4r::win::Feature>> features;
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
struct RrFeature {
|
||||
d4r::win::rr::Creation creation;
|
||||
d4r::win::rr::Backend backend;
|
||||
RrFeature(ID3D12Device* device, void* parameters, const std::string& directory)
|
||||
: creation(parameters), backend(device, creation, directory) {}
|
||||
};
|
||||
std::unordered_map<Handle*, std::shared_ptr<RrFeature>> rrFeatures;
|
||||
std::string rr_directory() {
|
||||
const char* backend = std::getenv("D4R_RR_BACKEND");
|
||||
if (!backend || std::string(backend) != "native")
|
||||
throw std::runtime_error("Native RR requires D4R_RR_BACKEND=native");
|
||||
return d4r::win::env_path("D4R_RR_MODEL_DIR");
|
||||
}
|
||||
bool rr_ready(ID3D12Device* device) {
|
||||
try {
|
||||
const auto directory = rr_directory();
|
||||
void* p = d4r_ngx_parameters_create();
|
||||
if (!p) return false;
|
||||
struct Cleanup { void* p; ~Cleanup() { d4r_ngx_parameters_destroy(p); } } cleanup{p};
|
||||
for (const char* name : {"Width", "Height", "OutWidth", "OutHeight"}) d4r_ngx_set_uint(p, name, 64);
|
||||
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", 3);
|
||||
d4r::win::rr::Creation creation(p);
|
||||
// Readiness checks the actual configured model's pipeline/weight load,
|
||||
// adapter features and packet/fence import. No synthetic frame runs.
|
||||
d4r::win::rr::Backend candidate(device, creation, directory);
|
||||
return true;
|
||||
} catch (const std::exception& error) {
|
||||
if (std::getenv("D4R_RR_BACKEND")) std::fprintf(stderr, "D4R_RR_UNAVAILABLE %s\n", error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
struct RrSnapshot {
|
||||
d4r::win::rr::Snapshot frame;
|
||||
std::array<d4r::win::ResourceAccess, d4r::win::rr::Snapshot::Count> access;
|
||||
std::array<bool, d4r::win::rr::Snapshot::Count> explicitState{};
|
||||
RrSnapshot(const d4r::win::rr::Creation& creation, void* p, ID3D12GraphicsCommandList* list)
|
||||
: frame(creation, p) {
|
||||
const char* names[] = {"D4R.Color.State", "D4R.Motion.State", "D4R.Depth.State", "D4R.Normals.State",
|
||||
"D4R.Roughness.State", "D4R.Diffuse.State", "D4R.Specular.State", "D4R.Output.State", "D4R.Exposure.State",
|
||||
"D4R.HitDistance.State", "D4R.Disocclusion.State", "D4R.Subsurface.State", "D4R.BeforeParticles.State"};
|
||||
for (size_t i = 0; i < access.size(); ++i) {
|
||||
if (!frame.resources[i]) {
|
||||
if (i < 8) throw std::runtime_error("Missing required NGX RR guide/output");
|
||||
continue;
|
||||
}
|
||||
const auto fallback = d4r::win::ngx_resource_access(p, names[i], i == 7 ?
|
||||
D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
unsigned declared = 0;
|
||||
explicitState[i] = d4r_ngx_get_uint(p, names[i], &declared) == 1;
|
||||
access[i] = list ? d4r::win::commands::resource_access(list, frame.resources[i].Get(), fallback) :
|
||||
d4r::win::commands::submitted_resource_access(frame.resources[i].Get(), fallback);
|
||||
if (access[i].pending_split) throw std::runtime_error("NGX RR texture has an unfinished split barrier");
|
||||
}
|
||||
if (frame.settings.roughnessPacked) {
|
||||
access[4] = access[3]; explicitState[4] = explicitState[3];
|
||||
}
|
||||
}
|
||||
void resolve() {
|
||||
for (size_t i = 0; i < access.size(); ++i) if (access[i].inherited) {
|
||||
access[i] = d4r::win::commands::submitted_resource_access(frame.resources[i].Get(), access[i]);
|
||||
// Explicit caller state remains valid when no prior submitted list
|
||||
// supplied a tracked transition for this resource.
|
||||
if (access[i].inherited && explicitState[i]) access[i].inherited = false;
|
||||
if (access[i].pending_split || access[i].inherited) throw std::runtime_error("NGX RR inherited texture state unresolved");
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
std::unordered_map<void*, bool> parameters;
|
||||
constexpr unsigned failure = 0xBAD00002, invalid = 0xBAD00005;
|
||||
unsigned call(const std::function<unsigned()>& fn) noexcept {
|
||||
@@ -27,7 +124,7 @@ unsigned call(const std::function<unsigned()>& fn) noexcept {
|
||||
}
|
||||
}
|
||||
unsigned allocate(void** out, bool caps) {
|
||||
if (!runtime || !out) return invalid;
|
||||
if (!initialization || !out) return invalid;
|
||||
void* value = d4r_ngx_parameters_create();
|
||||
if (!value) return failure;
|
||||
*out = nullptr;
|
||||
@@ -36,6 +133,15 @@ unsigned allocate(void** out, bool caps) {
|
||||
~LocalParameters() { if (p) d4r_ngx_parameters_destroy(p); }
|
||||
} owned{value};
|
||||
if (caps) {
|
||||
try { ensure_sr_runtime(); }
|
||||
catch (const std::exception& error) {
|
||||
if (!deferredSr) throw;
|
||||
std::fprintf(stderr, "D4R_SR_UNAVAILABLE %s\n", error.what());
|
||||
d4r_ngx_set_int(value, "SuperSampling.Available", 0);
|
||||
d4r_ngx_set_int(value, "SuperSampling.NeedsUpdatedDriver", 0);
|
||||
d4r_ngx_set_int(value, "SuperSampling.FeatureInitResult", int(failure));
|
||||
}
|
||||
if (runtime) {
|
||||
std::lock_guard<std::mutex> lock(runtime->mutex);
|
||||
runtime->current(); void* official = nullptr;
|
||||
d4r::win::ngx_check(runtime->capabilities(&official), "CUDA capabilities");
|
||||
@@ -48,6 +154,14 @@ unsigned allocate(void** out, bool caps) {
|
||||
void* callback = nullptr;
|
||||
if (d4r_ngx_get_void(official, name, &callback) == 1) d4r_ngx_set_void(value, name, callback);
|
||||
}
|
||||
}
|
||||
bool rrAvailable = false;
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
rrAvailable = rr_ready(initialization->device.Get());
|
||||
#endif
|
||||
d4r_ngx_set_int(value, "SuperSamplingDenoising.Available", rrAvailable ? 1 : 0);
|
||||
d4r_ngx_set_int(value, "SuperSamplingDenoising.NeedsUpdatedDriver", 0);
|
||||
d4r_ngx_set_int(value, "SuperSamplingDenoising.FeatureInitResult", rrAvailable ? 1 : int(0xBAD00001u));
|
||||
}
|
||||
parameters.emplace(value, true); *out = value; owned.p = nullptr;
|
||||
return 1;
|
||||
@@ -61,7 +175,7 @@ API unsigned NVSDK_NGX_D3D12_Init_Ext(unsigned long long app, const wchar_t* dat
|
||||
std::lock_guard<std::mutex> lock(apiMutex);
|
||||
return call([&] {
|
||||
d4r::win::player::ensure_environment();
|
||||
if (!runtime) runtime = std::make_shared<d4r::win::Runtime>(device, app, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info));
|
||||
initialize(device, app, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info));
|
||||
if (const char* backend = std::getenv("D4R_D3D12_COMMAND_BACKEND"); backend && std::string(backend) == "1")
|
||||
d4r::win::commands::install(device);
|
||||
return 1u;
|
||||
@@ -76,7 +190,7 @@ API unsigned NVSDK_NGX_D3D12_Init_ProjectID(const char* project, int engine, con
|
||||
if (!project || !*project) return invalid;
|
||||
d4r::win::player::ensure_environment();
|
||||
d4r::ngx::ProjectIdentity identity{project, engine, version ? version : ""};
|
||||
if (!runtime) runtime = std::make_shared<d4r::win::Runtime>(device, 0, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info), &identity);
|
||||
initialize(device, 0, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info), &identity);
|
||||
if (const char* backend = std::getenv("D4R_D3D12_COMMAND_BACKEND"); backend && std::string(backend) == "1") d4r::win::commands::install(device);
|
||||
return 1u;
|
||||
});
|
||||
@@ -86,7 +200,19 @@ API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const
|
||||
if (!adapter || !info || !requirements) return invalid;
|
||||
return call([&] {
|
||||
*requirements = {}; requirements->FeatureSupported = 16; // Unsupported feature.
|
||||
if (info->FeatureID != 1) return 1u;
|
||||
if (info->FeatureID != 1) {
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
if (info->FeatureID == 13) {
|
||||
d4r::win::player::ensure_environment();
|
||||
d4r::win::ComPtr<ID3D12Device> device;
|
||||
if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device))) && rr_ready(device.Get())) {
|
||||
requirements->FeatureSupported = 0;
|
||||
std::strcpy(requirements->MinOSVersion, "10.0.22000.0");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 1u;
|
||||
}
|
||||
d4r::win::player::ensure_environment();
|
||||
DXGI_ADAPTER_DESC desc{}; d4r::win::dx(adapter->GetDesc(&desc), "NGX requirements adapter");
|
||||
// OptiScaler can override the DXGI vendor ID. The physical HIP
|
||||
@@ -119,7 +245,18 @@ API unsigned NVSDK_NGX_D3D12_GetScratchBufferSize(unsigned, const void*, size_t*
|
||||
API unsigned NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned feature, void* p, Handle** out) {
|
||||
std::lock_guard<std::mutex> lock(apiMutex);
|
||||
return call([&] {
|
||||
if (!runtime || feature != 1 || !p || !out) return invalid;
|
||||
if (!initialization || !p || !out) return invalid;
|
||||
*out = nullptr;
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
if (feature == 13) {
|
||||
auto value = std::make_shared<RrFeature>(initialization->device.Get(), p, rr_directory());
|
||||
d4r::win::commands::install(initialization->device.Get());
|
||||
auto handle = std::make_unique<Handle>(); handle->Id = nextId++;
|
||||
rrFeatures.emplace(handle.get(), std::move(value)); *out = handle.release(); return 1u;
|
||||
}
|
||||
#endif
|
||||
if (feature != 1) return invalid;
|
||||
ensure_sr_runtime();
|
||||
d4r::win::player::apply_preset(p);
|
||||
auto value = std::make_shared<d4r::win::Feature>(runtime, p);
|
||||
auto handle = std::make_unique<Handle>(); handle->Id = nextId++;
|
||||
@@ -127,12 +264,37 @@ API unsigned NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned
|
||||
});
|
||||
}
|
||||
API unsigned d4r_D3D12_EvaluateAtBoundary(ID3D12CommandQueue* queue, Handle* handle, void* p) {
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
std::shared_ptr<RrFeature> rr;
|
||||
{ std::lock_guard<std::mutex> lock(apiMutex);
|
||||
auto it = rrFeatures.find(handle); if (it != rrFeatures.end()) rr = it->second; }
|
||||
if (rr) return call([&] {
|
||||
RrSnapshot snapshot(rr->creation, p, nullptr); snapshot.resolve();
|
||||
rr->backend.evaluate(queue, snapshot.frame, snapshot.access); return 1u;
|
||||
});
|
||||
#endif
|
||||
std::shared_ptr<d4r::win::Feature> feature;
|
||||
{ std::lock_guard<std::mutex> lock(apiMutex);
|
||||
auto it = features.find(handle); if (it == features.end() || !p) return invalid; feature = it->second; }
|
||||
return call([&] { feature->evaluate_boundary(queue, p); return 1u; });
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, const Handle* handle, void* p, void*) {
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
std::shared_ptr<RrFeature> rr;
|
||||
{ std::lock_guard<std::mutex> lock(apiMutex);
|
||||
auto it = rrFeatures.find(const_cast<Handle*>(handle)); if (it != rrFeatures.end()) rr = it->second; }
|
||||
if (rr) return call([&] {
|
||||
if (!p || !list) return invalid;
|
||||
auto snapshot = std::make_shared<RrSnapshot>(rr->creation, p, list);
|
||||
// Reject unsupported contracts before splitting the logical command
|
||||
// list. Submission still rechecks resources and resolved access.
|
||||
rr->backend.validate_frame(snapshot->frame);
|
||||
d4r::win::commands::record_boundary(list, [rr, snapshot](ID3D12CommandQueue* queue) {
|
||||
snapshot->resolve(); rr->backend.evaluate(queue, snapshot->frame, snapshot->access);
|
||||
});
|
||||
return 1u;
|
||||
});
|
||||
#endif
|
||||
std::shared_ptr<d4r::win::Feature> feature;
|
||||
{ std::lock_guard<std::mutex> lock(apiMutex);
|
||||
auto it = features.find(const_cast<Handle*>(handle));
|
||||
@@ -194,6 +356,13 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature_C(ID3D12GraphicsCommandList* list,
|
||||
return NVSDK_NGX_D3D12_EvaluateFeature(list, handle, p, callback);
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_ReleaseFeature(Handle* handle) {
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
std::shared_ptr<RrFeature> rr;
|
||||
{ std::lock_guard<std::mutex> lock(apiMutex);
|
||||
auto it = rrFeatures.find(handle);
|
||||
if (it != rrFeatures.end()) { rr = std::move(it->second); rrFeatures.erase(it); delete handle; } }
|
||||
if (rr) return call([&] { rr->backend.drain(); rr.reset(); return 1u; });
|
||||
#endif
|
||||
std::shared_ptr<d4r::win::Feature> feature;
|
||||
{ std::lock_guard<std::mutex> lock(apiMutex);
|
||||
auto it = features.find(handle); if (it == features.end()) return invalid;
|
||||
@@ -206,10 +375,15 @@ API unsigned NVSDK_NGX_D3D12_Shutdown() {
|
||||
std::lock_guard<std::mutex> lock(apiMutex);
|
||||
return call([&] {
|
||||
if (runtime && std::getenv("D4R_ASYNC_INTEROP")) runtime->drain_async_queue();
|
||||
#if defined(D4R_NATIVE_RR_BACKEND)
|
||||
for (auto& pair : rrFeatures) pair.second->backend.drain();
|
||||
for (auto& pair : rrFeatures) delete pair.first;
|
||||
rrFeatures.clear();
|
||||
#endif
|
||||
for (auto& pair : features) delete pair.first;
|
||||
features.clear();
|
||||
for (auto& pair : parameters) d4r_ngx_parameters_destroy(pair.first);
|
||||
parameters.clear(); runtime.reset(); return 1u;
|
||||
parameters.clear(); runtime.reset(); initialization.reset(); deferredSr = false; return 1u;
|
||||
});
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_Shutdown1(ID3D12Device*) { return NVSDK_NGX_D3D12_Shutdown(); }
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
"""Verify and deploy a locally built RR model; optionally select it in d4r.ini.
|
||||
|
||||
Existing models are preserved. Configuration changes receive an external backup.
|
||||
No NVIDIA DLL, compiler, driver, package archive or system setting is changed.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def select_native(data, model):
|
||||
bom = data.startswith(b'\xef\xbb\xbf')
|
||||
text = data.decode('utf-8-sig')
|
||||
newline = '\r\n' if '\r\n' in text else '\n'
|
||||
result, in_rr, found, pending = [], False, False, set()
|
||||
values = {'backend': 'native', 'modeldir': model}
|
||||
def flush():
|
||||
if pending and result and not result[-1].endswith(('\n', '\r')):
|
||||
result.append(newline)
|
||||
for key in ('backend', 'modeldir'):
|
||||
if key in pending:
|
||||
result.append(('Backend' if key == 'backend' else 'ModelDir') + ' = ' + values[key] + newline)
|
||||
pending.clear()
|
||||
for line in text.splitlines(keepends=True):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('['):
|
||||
if in_rr:
|
||||
flush()
|
||||
in_rr = stripped.split(']', 1)[0][1:].strip().casefold() == 'rayreconstruction'
|
||||
if in_rr:
|
||||
found = True
|
||||
pending = set(values)
|
||||
if in_rr and '=' in line and not stripped.startswith((';', '#')):
|
||||
key = line.split('=', 1)[0].strip().casefold()
|
||||
if key in values:
|
||||
prefix, body = line.split('=', 1)
|
||||
ending = '\r\n' if body.endswith('\r\n') else '\n' if body.endswith('\n') else ''
|
||||
body = body.removesuffix(ending) if ending else body
|
||||
comment = re.search(r'\s[;#]', body)
|
||||
suffix = body[comment.start():] if comment else ''
|
||||
result.append(prefix + '= ' + values[key] + suffix + ending)
|
||||
pending.discard(key)
|
||||
continue
|
||||
result.append(line)
|
||||
if in_rr:
|
||||
flush()
|
||||
if not found:
|
||||
if result and not result[-1].endswith('\n'):
|
||||
result.append(newline)
|
||||
result.extend((newline, '[RayReconstruction]' + newline, 'Backend = native' + newline,
|
||||
'ModelDir = ' + model + newline))
|
||||
return (b'\xef\xbb\xbf' if bom else b'') + ''.join(result).encode('utf-8')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--model', required=True, type=lambda p: Path(p).resolve())
|
||||
parser.add_argument('--runtime', required=True, type=lambda p: Path(p).resolve())
|
||||
parser.add_argument('--enable', action='store_true')
|
||||
parser.add_argument('--backup-root', type=lambda p: Path(p).resolve(),
|
||||
default=Path(os.environ.get('LOCALAPPDATA', str(Path.home()))) / 'd4r/backups')
|
||||
args = parser.parse_args()
|
||||
config = args.runtime / 'd4r.ini'
|
||||
if not config.is_file():
|
||||
parser.error('--runtime must contain d4r.ini')
|
||||
if args.enable and args.backup_root.resolve().is_relative_to(args.runtime.parent):
|
||||
parser.error('configuration backups must stay outside the game/runtime parent directory')
|
||||
cache_data = (args.model / 'windows-native-python-cache.json').read_bytes()
|
||||
cache = json.loads(cache_data)
|
||||
identity = cache['identity']
|
||||
if identity.get('preset') not in ('D', 'E') or identity.get('architecture') != 'gfx1101' or identity.get('arithmetic') != 'exact-gfx11':
|
||||
raise RuntimeError('Unsupported local RR cache identity')
|
||||
products = cache['products']
|
||||
if not {'rr.txt', 'enc0.args', 'hkpn.args'}.issubset(products):
|
||||
raise RuntimeError('RR cache omits required manifest/templates')
|
||||
for name, expected in products.items():
|
||||
if Path(name).name != name or name in ('.', '..') or '\\' in name or ':' in name:
|
||||
raise RuntimeError('Invalid model product path: ' + name)
|
||||
if sha((args.model / name).read_bytes()) != expected:
|
||||
raise RuntimeError('Model product changed: ' + name)
|
||||
manifest = (args.model / 'rr.txt').read_text()
|
||||
stages = [line.split() for line in manifest.splitlines() if line.startswith('stage ')]
|
||||
expected_stages = {'enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0', 'hkpn'}
|
||||
if (not manifest.startswith('D4RR0001\n') or
|
||||
f'preset {4 if identity["preset"] == "D" else 5}' not in manifest.splitlines() or
|
||||
len(stages) != 12 or {fields[1] for fields in stages} != expected_stages or 'arithmetic gfx11' not in manifest):
|
||||
raise RuntimeError('Incomplete RR model manifest')
|
||||
for fields in stages:
|
||||
if fields[2] not in products or fields[4] not in products:
|
||||
raise RuntimeError('Stage product not covered by cache')
|
||||
name = 'rr-' + identity['preset'].lower() + '-' + sha(cache_data)[:12]
|
||||
destination = args.runtime / 'engine' / name
|
||||
if not destination.resolve().is_relative_to(args.runtime):
|
||||
raise RuntimeError('RR destination must stay within the selected runtime')
|
||||
reused = destination.exists()
|
||||
if not reused:
|
||||
destination.mkdir(parents=True)
|
||||
for product in products:
|
||||
shutil.copy2(args.model / product, destination / product)
|
||||
(destination / 'd4r-private-rr.json').write_text(json.dumps({'privateLocalModel': True, 'cacheSha256': sha(cache_data), 'products': products}, indent=2) + '\n')
|
||||
for product, expected in products.items():
|
||||
if sha((destination / product).read_bytes()) != expected:
|
||||
raise RuntimeError('Existing/deployed RR model differs; preserving it: ' + str(destination))
|
||||
backup = None
|
||||
if args.enable:
|
||||
original = config.read_bytes()
|
||||
updated = select_native(original, destination.relative_to(args.runtime).as_posix())
|
||||
if updated != original:
|
||||
args.backup_root.mkdir(parents=True, exist_ok=True)
|
||||
backup = args.backup_root / ('rr-config-' + uuid.uuid4().hex + '.ini')
|
||||
with backup.open('xb') as stream:
|
||||
stream.write(original)
|
||||
temporary = config.with_name('d4r.ini.' + uuid.uuid4().hex + '.tmp')
|
||||
with temporary.open('xb') as stream:
|
||||
stream.write(updated)
|
||||
if config.read_bytes() != original:
|
||||
temporary.unlink()
|
||||
raise RuntimeError('d4r.ini changed during deployment; preserved current configuration')
|
||||
os.replace(temporary, config)
|
||||
print(json.dumps({'deployed': str(destination), 'preset': identity['preset'], 'productsVerified': len(products),
|
||||
'reused': reused, 'enabled': args.enable, 'configBackup': str(backup) if backup else None,
|
||||
'privateLocalModel': True, 'published': False}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -119,6 +119,7 @@ struct Args {
|
||||
std::string pixel_profile = "baseline";
|
||||
std::string barrier_mode = "legacy";
|
||||
bool early_indirect = false;
|
||||
bool player_mode = false;
|
||||
std::string ngx_core, dlss_dll;
|
||||
std::string nvapi_dll;
|
||||
std::string ngx_abi = "driver";
|
||||
@@ -160,6 +161,7 @@ struct Args {
|
||||
barrier_mode = value;
|
||||
}
|
||||
else if (key == "--early-indirect") early_indirect = value == "1";
|
||||
else if (key == "--player-mode") player_mode = value == "1";
|
||||
else if (key == "--ngx-core") ngx_core = value;
|
||||
else if (key == "--dlss-dll") dlss_dll = value;
|
||||
else if (key == "--nvapi-dll") nvapi_dll = value;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_4.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
// Native Windows uses the installed driver's normal D3D12 path. Do not import
|
||||
// Proton's amdxc64 shim or Linux PCI/sysfs discovery into this process.
|
||||
inline bool d4r_fsr_rr_driver_extension(ID3D12Device* device, std::string& detail)
|
||||
{
|
||||
LUID luid{};
|
||||
device->GetAdapterLuid(&luid);
|
||||
IDXGIFactory4* factory = nullptr;
|
||||
if (FAILED(CreateDXGIFactory2(0, IID_PPV_ARGS(&factory)))) {
|
||||
detail = "native Windows: DXGI factory query failed";
|
||||
return false;
|
||||
}
|
||||
IDXGIAdapter1* adapter = nullptr;
|
||||
const HRESULT result = factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(&adapter));
|
||||
factory->Release();
|
||||
if (FAILED(result)) {
|
||||
detail = "native Windows: device adapter query failed";
|
||||
return false;
|
||||
}
|
||||
DXGI_ADAPTER_DESC1 desc{};
|
||||
const HRESULT described = adapter->GetDesc1(&desc);
|
||||
adapter->Release();
|
||||
if (FAILED(described)) {
|
||||
detail = "native Windows: adapter description failed";
|
||||
return false;
|
||||
}
|
||||
char text[160];
|
||||
std::snprintf(text, sizeof(text), "native Windows DXGI %04x:%04x; installed D3D12 driver path",
|
||||
desc.VendorId, desc.DeviceId);
|
||||
detail = text;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Adapt textual HIP helper IR to the pinned LLVM 22 reader without relaxing FP."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
|
||||
|
||||
def float_hex(match):
|
||||
bits = int(match[1], 16)
|
||||
sign, exponent, fraction = bits >> 31, (bits >> 23) & 255, bits & 0x7fffff
|
||||
if exponent:
|
||||
wide_exponent = 2047 if exponent == 255 else exponent + 896
|
||||
wide_fraction = fraction << 29
|
||||
elif fraction:
|
||||
leading = fraction.bit_length() - 1
|
||||
wide_exponent = leading - 149 + 1023
|
||||
wide_fraction = (fraction - (1 << leading)) << (52 - leading)
|
||||
else:
|
||||
wide_exponent = wide_fraction = 0
|
||||
# LLVM's legacy float spelling uses the exact widening's binary64 bits.
|
||||
# Integer operations preserve signed zero and signaling/quiet NaN payloads.
|
||||
return f'0x{((sign << 63) | (wide_exponent << 52) | wide_fraction):016X}'
|
||||
|
||||
|
||||
def compatible_ir(text):
|
||||
text = re.sub(r'\bf0x([0-9A-Fa-f]{16})\b', r'0x\1', text)
|
||||
text = re.sub(r'\bf0x([0-9A-Fa-f]{4})\b', r'0xH\1', text)
|
||||
text = re.sub(r'\bf0x([0-9A-Fa-f]{8})\b', float_hex, text)
|
||||
text = text.replace('denormal_fpenv(dynamic)', '"denormal-fp-math"="dynamic,dynamic"')
|
||||
# This newer optimization promise is absent in LLVM 22. Dropping a promise
|
||||
# weakens what the optimizer may assume; it does not change instructions.
|
||||
text = re.sub(r'\bnocreateundeforpoison\b', '', text)
|
||||
text = re.sub(r'\bmemory\([^)]*\btarget_mem\b[^)]*\)', '', text)
|
||||
text = re.sub(r'\b(uitofp|sitofp|fptoui|fptosi)\s+(?:(?:nnan|ninf|nsz|arcp|contract|afn|reassoc|fast)\s+)+',
|
||||
r'\1 ', text)
|
||||
# LLVM 24 permits short decimal floats that the older reader rejects.
|
||||
# Restrict conversion to unquoted literals whose nearest IR type is float/half;
|
||||
# double literals and string attributes retain their original spelling.
|
||||
result = []
|
||||
decimal = re.compile(r'(?<![\w.])[-+]?\d+\.\d+(?:[eE][-+]?\d+)?(?![\w.])')
|
||||
for line in text.splitlines():
|
||||
pieces = re.split(r'("(?:[^"\\]|\\.)*")', line)
|
||||
for index in range(0, len(pieces), 2):
|
||||
piece = pieces[index]
|
||||
def convert(match):
|
||||
types = re.findall(r'\b(float|double|half|bfloat|fp128|i\d+)\b', piece[:match.start()])
|
||||
if not types or types[-1] not in ('float', 'half'):
|
||||
return match[0]
|
||||
if types[-1] == 'half':
|
||||
bits = struct.unpack('>H', struct.pack('>e', float(match[0])))[0]
|
||||
return f'0xH{bits:04X}'
|
||||
bits = struct.unpack('>I', struct.pack('>f', float(match[0])))[0]
|
||||
return float_hex(re.match(r'(.*)', f'{bits:08X}'))
|
||||
pieces[index] = decimal.sub(convert, piece)
|
||||
result.append(''.join(pieces))
|
||||
return '\n'.join(result) + '\n'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('input', type=Path)
|
||||
parser.add_argument('output', type=Path)
|
||||
args = parser.parse_args()
|
||||
args.output.write_text(compatible_ir(args.input.read_text()))
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
struct ID3D12Resource;
|
||||
extern "C" {
|
||||
void d4r_ngx_set_ull(void*, const char*, unsigned long long);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include "ngx_windows_runtime.h"
|
||||
|
||||
namespace d4r::win {
|
||||
// Own the complete Init contract when native RR defers the independent SR
|
||||
// runtime. The caller may mutate or free its strings/path list after Init.
|
||||
struct RuntimeInitialization {
|
||||
ComPtr<ID3D12Device> device;
|
||||
unsigned long long application;
|
||||
unsigned sdk;
|
||||
std::wstring data;
|
||||
ngx::FeatureCommonInfo common{};
|
||||
std::vector<std::wstring> paths;
|
||||
std::vector<const wchar_t*> pathPointers;
|
||||
ngx::ProjectIdentity project;
|
||||
bool hasCommon = false, hasProject = false;
|
||||
RuntimeInitialization(ID3D12Device* d3d, unsigned long long app, const wchar_t* directory, unsigned version,
|
||||
const ngx::FeatureCommonInfo* info, const ngx::ProjectIdentity* identity)
|
||||
: device(d3d), application(app), sdk(version), data(directory && *directory ? directory : std::filesystem::current_path().wstring()) {
|
||||
if (!d3d) throw std::runtime_error("Null NGX initialization device");
|
||||
if (identity) { project = *identity; hasProject = true; }
|
||||
if (info) {
|
||||
if (info->PathListInfo.Length > 1024 || (info->PathListInfo.Length && !info->PathListInfo.Path))
|
||||
throw std::runtime_error("Invalid NGX initialization path list");
|
||||
hasCommon = true;
|
||||
// Runtime consumes only path and logging information. InternalData
|
||||
// is opaque and must not become a deferred borrowed pointer.
|
||||
if (sdk >= 0x14) common.LoggingInfo = info->LoggingInfo;
|
||||
for (unsigned i = 0; i < info->PathListInfo.Length; ++i)
|
||||
if (info->PathListInfo.Path[i]) paths.emplace_back(info->PathListInfo.Path[i]);
|
||||
for (const auto& path : paths) pathPointers.push_back(path.c_str());
|
||||
common.PathListInfo = {pathPointers.data(), unsigned(pathPointers.size())};
|
||||
}
|
||||
}
|
||||
RuntimeInitialization(const RuntimeInitialization&) = delete;
|
||||
RuntimeInitialization& operator=(const RuntimeInitialization&) = delete;
|
||||
std::shared_ptr<Runtime> create() const {
|
||||
return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk,
|
||||
hasCommon ? &common : nullptr, hasProject ? &project : nullptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -26,6 +26,9 @@ struct Settings {
|
||||
bool nativeFp8 = false;
|
||||
bool asyncInterop = false, validateOutput = false, verbose = false, showWatermark = false;
|
||||
std::wstring cacheDir;
|
||||
std::wstring nativeDir;
|
||||
std::string rrBackend = "off";
|
||||
std::wstring rrModelDir;
|
||||
};
|
||||
struct State {
|
||||
bool active = false;
|
||||
@@ -122,6 +125,17 @@ inline Settings read_settings(const std::vector<IniEntry>& ini, std::vector<std:
|
||||
notes.push_back("d4r.ini: [Debug] Log must be normal or verbose, not '" + entry.value + "'; using normal");
|
||||
} else if (name == "paths.cachedir") {
|
||||
if (!entry.value.empty() && lower(entry.value) != "auto") settings.cacheDir = diag::wide(entry.value);
|
||||
} else if (name == "paths.nativedir") {
|
||||
if (!entry.value.empty() && lower(entry.value) != "auto") settings.nativeDir = diag::wide(entry.value);
|
||||
} else if (name == "rayreconstruction.backend") {
|
||||
const auto value = lower(entry.value);
|
||||
if (value.empty() || value == "auto" || value == "off") settings.rrBackend = "off";
|
||||
else if (value == "native" || value == "fsr") {
|
||||
settings.rrBackend = value;
|
||||
if (value == "fsr") notes.push_back("d4r.ini: native Windows FSR RR is unavailable; RR requests will be rejected");
|
||||
} else { settings.rrBackend = "off"; notes.push_back("d4r.ini: [RayReconstruction] Backend must be off, native or fsr; using off"); }
|
||||
} else if (name == "rayreconstruction.modeldir") {
|
||||
settings.rrModelDir = !entry.value.empty() && lower(entry.value) != "auto" ? diag::wide(entry.value) : L"";
|
||||
} else notes.push_back("d4r.ini: [" + entry.section + "] " + entry.key + " is not a Windows setting; ignored");
|
||||
}
|
||||
return settings;
|
||||
@@ -192,14 +206,31 @@ inline void verify_dll(const std::filesystem::path& dir, const std::vector<Pin>&
|
||||
|
||||
// The installer used to check these before touching the game; a drag-in
|
||||
// install checks them at startup instead.
|
||||
inline std::filesystem::path native_code_object_dir(const std::filesystem::path& dir) {
|
||||
inline std::filesystem::path native_code_object_dir(const std::filesystem::path& dir, const Settings* settings = nullptr) {
|
||||
if (settings && !settings->nativeDir.empty()) {
|
||||
auto root = std::filesystem::path(settings->nativeDir);
|
||||
if (root.is_relative()) root = dir / root;
|
||||
const auto arch = root / diag::wide(diag::HipApi::target_arch());
|
||||
return std::filesystem::is_directory(arch) ? arch : root;
|
||||
}
|
||||
const auto arch = dir / L"native" / diag::wide(diag::HipApi::target_arch());
|
||||
return std::filesystem::is_directory(arch) ? arch : dir / L"native";
|
||||
}
|
||||
inline void verify_code_objects(const std::filesystem::path& dir) {
|
||||
inline void verify_code_objects(const std::filesystem::path& dir, const Settings* settings = nullptr) {
|
||||
const std::string arch = diag::HipApi::target_arch();
|
||||
std::vector<std::filesystem::path> objects{dir / (L"pixel_convert_" + diag::wide(arch) + L".hsaco")};
|
||||
const auto native = native_code_object_dir(dir);
|
||||
const auto native = native_code_object_dir(dir, settings);
|
||||
if (settings && !settings->nativeDir.empty() &&
|
||||
(!std::filesystem::is_directory(native) || !std::filesystem::is_regular_file(native / L"d4r-kernels.txt")))
|
||||
throw std::runtime_error("[Paths] NativeDir must contain the complete native kernel set and d4r-kernels.txt");
|
||||
if (settings && !settings->nativeDir.empty()) {
|
||||
for (const char* name : {"dltss_pwin_enc0_layer", "dltss_pwin_enc1_layer", "dltss_pwin_enc2_layer",
|
||||
"dltss_pwin_enc3_layer", "dltss_pwin_enc4_layer", "dltss_pwin_dec5_layer", "dltss_pwin_dec4_layer",
|
||||
"dltss_pwin_dec3_layer", "dltss_pwin_dec2_layer", "dltss_pwin_dec1_layer", "dltss_pwin_dec0_layer",
|
||||
"rrlite_enc1_4x4", "rrlite_enc2_4x4", "rrlite_enc3_tube_4x4", "rrlite_dec2_4x4", "rrlite_dec1_4x4"})
|
||||
if (!std::filesystem::is_regular_file(native / (std::string(name) + ".hsaco")))
|
||||
throw std::runtime_error(std::string("[Paths] NativeDir is incomplete; missing ") + name);
|
||||
}
|
||||
if (!std::filesystem::is_directory(native)) throw std::runtime_error("d4r\\native (or d4r\\native\\" + arch + ") is missing; extract the d4r ZIP again");
|
||||
for (const auto& entry : std::filesystem::directory_iterator(native))
|
||||
if (entry.path().extension() == L".hsaco") objects.push_back(entry.path());
|
||||
@@ -212,9 +243,9 @@ inline void verify_code_objects(const std::filesystem::path& dir) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void verify_native_fp8_bundle(const std::filesystem::path& dir) {
|
||||
inline void verify_native_fp8_bundle(const std::filesystem::path& dir, const Settings* settings = nullptr) {
|
||||
const auto arch = diag::HipApi::target_arch();
|
||||
const auto marker = native_code_object_dir(dir) / L"d4r-native-fp8.txt";
|
||||
const auto marker = native_code_object_dir(dir, settings) / L"d4r-native-fp8.txt";
|
||||
if (!std::filesystem::is_regular_file(marker))
|
||||
throw std::runtime_error("[Kernels] NativeFp8=true needs a matching gfx12 token-lane M bundle; rebuild with -TokenLanes and restart.");
|
||||
const auto contents = read_text(marker);
|
||||
@@ -260,6 +291,10 @@ inline void redirect(FILE* crt, DWORD stream, const std::filesystem::path& path)
|
||||
SetStdHandle(stream, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(crt))));
|
||||
}
|
||||
|
||||
inline std::filesystem::path rr_model_directory(const std::filesystem::path& dir, const Settings& settings) {
|
||||
const std::filesystem::path model = settings.rrModelDir.empty() ? std::filesystem::path(L"engine/rr-d") : std::filesystem::path(settings.rrModelDir);
|
||||
return std::filesystem::absolute(model.is_relative() ? dir / model : model).lexically_normal();
|
||||
}
|
||||
inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
std::filesystem::path cache = settings.cacheDir;
|
||||
if (cache.empty()) {
|
||||
@@ -276,6 +311,8 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
const auto compatNvapi = compat_nvapi_path(dir);
|
||||
// Same codegen and paths as windows-game.ps1; only diagnostics differ.
|
||||
const std::pair<const wchar_t*, std::optional<std::wstring>> values[] = {
|
||||
{L"D4R_RR_BACKEND", settings.rrBackend == "off" ? unset : std::optional<std::wstring>(diag::wide(settings.rrBackend))},
|
||||
{L"D4R_RR_MODEL_DIR", settings.rrBackend == "native" ? std::optional<std::wstring>(rr_model_directory(dir, settings).wstring()) : unset},
|
||||
{L"D4R_HIP_ROOT", p(L"hip")}, {L"HIP_PATH", p(L"hip")},
|
||||
{L"D4R_NVCUDA_DLL", p(L"zluda\\nvcuda.dll")}, {L"ZLUDA_CUDA_LIB", p(L"zluda\\nvcuda.dll")},
|
||||
{L"D4R_NVAPI_DLL", std::optional<std::wstring>(compatNvapi.wstring())},
|
||||
@@ -283,7 +320,7 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
{L"D4R_NGX_CORE", p(L"ngx\\_nvngx.dll")}, {L"D4R_DLSS_DLL", p(L"dlss\\nvngx_dlss.dll")},
|
||||
{L"D4R_NGX_DATA_DIR", std::optional<std::wstring>(ngxData.wstring())},
|
||||
{L"D4R_FORMAT_MODULE", p(L"pixel_convert_" + diag::wide(diag::HipApi::target_arch()) + L".hsaco")},
|
||||
{L"D4R_ZLUDA_NATIVE_DIR", std::optional<std::wstring>(native_code_object_dir(dir).wstring())},{L"D4R_D3D12_COMMAND_BACKEND", on},
|
||||
{L"D4R_ZLUDA_NATIVE_DIR", std::optional<std::wstring>(native_code_object_dir(dir, &settings).wstring())},{L"D4R_D3D12_COMMAND_BACKEND", on},
|
||||
{L"D4R_ZLUDA_WMMA", on}, {L"D4R_ZLUDA_WMMA_FP8", on},
|
||||
{L"D4R_ZLUDA_WMMA_FP8_NATIVE", settings.nativeFp8 ? on : off},
|
||||
{L"D4R_ZLUDA_WMMA_F16_REFERENCE", on},
|
||||
@@ -328,8 +365,8 @@ inline void ensure_environment() {
|
||||
verify_dll(dir, pins, "dlss", L"dlss\\nvngx_dlss.dll", "nvngx_dlss.dll", current.notes);
|
||||
verify_dll(dir, pins, "ngx", L"ngx\\_nvngx.dll", "_nvngx.dll", current.notes);
|
||||
reject_translation_layer();
|
||||
if (settings.nativeFp8) verify_native_fp8_bundle(dir);
|
||||
verify_code_objects(dir);
|
||||
if (settings.nativeFp8) verify_native_fp8_bundle(dir, &settings);
|
||||
verify_code_objects(dir, &settings);
|
||||
apply(dir, settings);
|
||||
current.active = true; current.preset = settings.preset;
|
||||
const char* model = settings.preset == 5 ? "E" : settings.preset == 12 ? "L" : settings.preset == 13 ? "M" : "K";
|
||||
|
||||
@@ -60,6 +60,28 @@ int wmain(int argc, wchar_t** argv) {
|
||||
expect(!s.showWatermark && has_note(notes, "ShowWatermark must be true or false"), "invalid ShowWatermark falls back to hidden with a note");
|
||||
s = settings("[Paths]\nCacheDir = D:\\d4r-cache\n", notes);
|
||||
expect(s.cacheDir == L"D:\\d4r-cache" && notes.empty(), "CacheDir path");
|
||||
s = settings("[Paths]\nNativeDir = private-native\n", notes);
|
||||
expect(s.nativeDir == L"private-native" && notes.empty(), "NativeDir path");
|
||||
expect(player::native_code_object_dir(dir, &s) == dir / L"private-native", "relative NativeDir resolves beside d4r.ini");
|
||||
expect(!error_of([&] { player::verify_code_objects(dir, &s); }).empty(), "incomplete private native directory rejected");
|
||||
s.cacheDir = (dir / L"cache").wstring();
|
||||
player::apply(dir, s);
|
||||
expect(std::wstring(_wgetenv(L"D4R_ZLUDA_NATIVE_DIR")) == (dir / L"private-native").wstring(),
|
||||
"player environment selects configured private native directory");
|
||||
s = settings("[Paths]\nNativeDir = auto\n", notes);
|
||||
expect(s.nativeDir.empty() && notes.empty(), "NativeDir auto preserves packaged kernels");
|
||||
s = settings("[RayReconstruction]\nBackend = NATIVE\nModelDir = local-rr\n", notes);
|
||||
expect(s.rrBackend == "native" && s.rrModelDir == L"local-rr" && notes.empty(), "native RR settings parse independently of SR");
|
||||
expect(player::rr_model_directory(dir, s) == std::filesystem::absolute(dir / L"local-rr").lexically_normal(), "relative RR model resolves beside d4r.ini");
|
||||
s.cacheDir = (dir / L"cache").wstring(); player::apply(dir, s);
|
||||
expect(std::string(std::getenv("D4R_RR_BACKEND")) == "native" &&
|
||||
std::filesystem::path(_wgetenv(L"D4R_RR_MODEL_DIR")) == player::rr_model_directory(dir, s), "native RR environment selected without a launcher");
|
||||
s = settings("[RayReconstruction]\nBackend = fsr\n", notes);
|
||||
expect(s.rrBackend == "fsr" && has_note(notes, "FSR RR is unavailable"), "unavailable FSR selection remains explicit");
|
||||
s = settings("[RayReconstruction]\nBackend = typo\n", notes);
|
||||
expect(s.rrBackend == "off" && has_note(notes, "Backend must be"), "invalid RR backend stays off");
|
||||
s = settings("", notes); s.cacheDir = (dir / L"cache").wstring(); player::apply(dir, s);
|
||||
expect(!std::getenv("D4R_RR_BACKEND") && !std::getenv("D4R_RR_MODEL_DIR"), "default RR off clears stale selection");
|
||||
|
||||
std::filesystem::create_directories(dir / L"nvapi");
|
||||
{ std::ofstream(dir / L"nvapi" / L"nvapi64.dll", std::ios::binary) << "generic nvapi fixture"; }
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Prepare a private portable RR model-tool bundle from pinned dependencies.
|
||||
|
||||
Downloads/extracts tools locally. Does not install Python, use pip, modify PATH,
|
||||
change policy, build a model, or install into a game.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
import shutil
|
||||
import subprocess
|
||||
import urllib.request
|
||||
import uuid
|
||||
import zipfile
|
||||
|
||||
|
||||
DEPENDENCIES = {
|
||||
'python-3.12.10-embed-amd64.zip': (
|
||||
'https://www.python.org/ftp/python/3.12.10/python-3.12.10-embed-amd64.zip',
|
||||
'4acbed6dd1c744b0376e3b1cf57ce906f9dc9e95e68824584c8099a63025a3c3'),
|
||||
'numpy-2.4.6-cp312-cp312-win_amd64.whl': (
|
||||
'https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl',
|
||||
'd8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751'),
|
||||
'glslang-16.6.0-windows-x86_64-release.zip': (
|
||||
'https://github.com/KhronosGroup/glslang/releases/download/16.6.0/glslang-16.6.0-windows-x86_64-release.zip',
|
||||
'82bf434e69b9bb4829de7e2b4bc2c5e7a7861e53d66cf75e5cc70f5f694a8d9b'),
|
||||
'glslang-16.6.0-LICENSE.txt': (
|
||||
'https://raw.githubusercontent.com/KhronosGroup/glslang/16.6.0/LICENSE.txt',
|
||||
'17e70c676e1521ff3e4686f04a2053d93a7e28a33be8de7ec37ab0ff72feb677'),
|
||||
}
|
||||
|
||||
|
||||
def sha(path):
|
||||
with path.open('rb') as stream:
|
||||
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
||||
|
||||
|
||||
def extract(archive, destination):
|
||||
with zipfile.ZipFile(archive) as source:
|
||||
for entry in source.infolist():
|
||||
name = PurePosixPath(entry.filename)
|
||||
if name.is_absolute() or '..' in name.parts or '\\' in entry.filename or ':' in entry.filename:
|
||||
raise RuntimeError('Unsafe dependency archive path')
|
||||
if entry.is_dir():
|
||||
continue
|
||||
target = destination.joinpath(*name.parts)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with source.open(entry) as data, target.open('xb') as out:
|
||||
shutil.copyfileobj(data, out)
|
||||
|
||||
|
||||
def main():
|
||||
repo = Path(__file__).resolve().parents[2]
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve())
|
||||
parser.add_argument('--archive', type=lambda p: Path(p).resolve(), help='optional clean portable-tools ZIP, before any model build')
|
||||
parser.add_argument('--download-cache', type=lambda p: Path(p).resolve(), default=repo / '.tools/downloads')
|
||||
args = parser.parse_args()
|
||||
if args.output.exists() or not args.output.is_relative_to((repo / 'build').resolve()):
|
||||
parser.error('use a fresh output directory under ignored build/')
|
||||
if args.archive and (args.archive.exists() or not args.archive.is_relative_to((repo / 'build').resolve()) or args.archive.is_relative_to(args.output)):
|
||||
parser.error('use a fresh archive under build/, outside the bundle directory')
|
||||
args.download_cache.mkdir(parents=True, exist_ok=True)
|
||||
for name, (url, expected) in DEPENDENCIES.items():
|
||||
path = args.download_cache / name
|
||||
if not path.exists():
|
||||
print('RR_TOOLS_DOWNLOAD ' + name, flush=True)
|
||||
temporary = path.with_name(name + '.part-' + uuid.uuid4().hex)
|
||||
with urllib.request.urlopen(url, timeout=60) as response, temporary.open('xb') as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
if sha(temporary) != expected:
|
||||
raise RuntimeError('Dependency download hash mismatch: ' + name)
|
||||
temporary.rename(path)
|
||||
if sha(path) != expected:
|
||||
raise RuntimeError('Cached dependency changed: ' + name)
|
||||
python = args.output / 'python'
|
||||
recipe = args.output / 'recipe'
|
||||
extract(args.download_cache / 'python-3.12.10-embed-amd64.zip', python)
|
||||
extract(args.download_cache / 'numpy-2.4.6-cp312-cp312-win_amd64.whl', recipe / '.tools/python/vendor')
|
||||
glslang = recipe / '.tools/glslang-16.6.0'
|
||||
extract(args.download_cache / 'glslang-16.6.0-windows-x86_64-release.zip', glslang)
|
||||
shutil.copy2(glslang / 'bin/glslang.exe', glslang / 'bin/glslangValidator.exe')
|
||||
if sha(glslang / 'bin/glslangValidator.exe') != '9643709c02c83e26773f96b6a87f197ab2e84f78be0879ad8fb3619418b5d9ad':
|
||||
raise RuntimeError('Extracted glslang identity mismatch')
|
||||
files = subprocess.check_output(['git', 'ls-files', '-z', 'engine', 'vulkan', 'kernels/tools'], cwd=repo).decode().split('\0')
|
||||
suffixes = {'.py', '.comp', '.glsl', '.h', '.inc', '.sh'}
|
||||
sources = [repo / name for name in files if name and Path(name).suffix in suffixes]
|
||||
sources += [repo / 'engine/rr/native_kernels.txt', repo / 'tools/windows/build_rr_model.py', repo / 'tools/windows/deploy_rr_model.py', repo / 'LICENSE']
|
||||
for source in sources:
|
||||
target = recipe / source.relative_to(repo)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target)
|
||||
# Keep isolated imports: no user site-packages or PYTHONPATH dependence.
|
||||
paths = ['python312.zip', '.', '../recipe', '../recipe/engine', '../recipe/engine/rr',
|
||||
'../recipe/kernels/tools', '../recipe/vulkan', '../recipe/tools/windows', '../recipe/.tools/python/vendor']
|
||||
(python / 'python312._pth').write_text('\n'.join(paths) + '\n')
|
||||
licenses = args.output / 'licenses'
|
||||
licenses.mkdir()
|
||||
shutil.copy2(args.download_cache / 'glslang-16.6.0-LICENSE.txt', licenses / 'glslang-LICENSE.txt')
|
||||
shutil.copy2(python / 'LICENSE.txt', licenses / 'Python-LICENSE.txt')
|
||||
for source in (recipe / '.tools/python/vendor/numpy-2.4.6.dist-info').glob('licenses/*'):
|
||||
if source.is_file():
|
||||
shutil.copy2(source, licenses / ('numpy-' + source.name))
|
||||
manifest = {'kind': 'local-rr-model-tools', 'dependencies': {name: {'url': url, 'sha256': digest}
|
||||
for name, (url, digest) in DEPENDENCIES.items()}, 'sourceWorkingTree': 'local modified; file hashes authoritative',
|
||||
'files': {path.relative_to(args.output).as_posix(): sha(path) for path in sorted(args.output.rglob('*')) if path.is_file()},
|
||||
'modelsBundled': False, 'nvidiaBinariesBundled': False}
|
||||
(args.output / 'rr-tools.json').write_text(json.dumps(manifest, indent=2) + '\n')
|
||||
if args.archive:
|
||||
args.archive.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Package only the recorded pristine tool inputs. Never sweep a used
|
||||
# bundle's model-build directory into a distributable archive.
|
||||
with zipfile.ZipFile(args.archive, 'x', zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
|
||||
for name in (*manifest['files'], 'rr-tools.json'):
|
||||
archive.write(args.output / name, name)
|
||||
with zipfile.ZipFile(args.archive) as archive:
|
||||
for name, expected in manifest['files'].items():
|
||||
if hashlib.sha256(archive.read(name)).hexdigest() != expected:
|
||||
raise RuntimeError('Portable-tools ZIP hash mismatch: ' + name)
|
||||
print('RR_TOOLS_ARCHIVE sha256=' + sha(args.archive) + ' bytes=' + str(args.archive.stat().st_size))
|
||||
print('RR_TOOLS_READY ' + str(args.output))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Refresh the verified gfx1101 development ZIP. Never creates a public release."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
import shutil
|
||||
import subprocess
|
||||
import zipfile
|
||||
|
||||
|
||||
def sha(path):
|
||||
with path.open('rb') as stream:
|
||||
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
for name in ('base-zip', 'shim', 'output', 'archive'):
|
||||
parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve())
|
||||
args = parser.parse_args()
|
||||
repo = Path(__file__).resolve().parents[2]
|
||||
build = (repo / 'build').resolve()
|
||||
for path in (args.output, args.archive):
|
||||
if not path.is_relative_to(build) or path.exists():
|
||||
parser.error('use fresh output/archive paths under ignored build/')
|
||||
if args.archive.is_relative_to(args.output):
|
||||
parser.error('archive must be outside the package directory')
|
||||
base_hash = sha(args.base_zip)
|
||||
if base_hash != 'e93be288f4013616cc2229be0132b40395d5031d8733bfa63886ba75ce1389ca':
|
||||
parser.error('base must be the verified local v3 ZIP')
|
||||
with zipfile.ZipFile(args.base_zip) as base:
|
||||
manifest = json.loads(base.read('d4r/package.json').decode('utf-8-sig'))
|
||||
if not manifest.get('localDevelopment') or set(manifest['targets']) != {'gfx1101'}:
|
||||
raise RuntimeError('Unexpected base package identity')
|
||||
records = {entry['path']: entry for entry in manifest['files']}
|
||||
names = [entry.filename for entry in base.infolist() if not entry.is_dir()]
|
||||
if len(names) != len(set(names)) or set(names) != set(records) | {'d4r/package.json'}:
|
||||
raise RuntimeError('Base ZIP entries differ from manifest')
|
||||
for name in names:
|
||||
parts = PurePosixPath(name)
|
||||
if parts.is_absolute() or '..' in parts.parts or '\\' in name or ':' in name:
|
||||
raise RuntimeError('Unsafe ZIP path: ' + name)
|
||||
data = base.read(name)
|
||||
if name in records and hashlib.sha256(data).hexdigest().upper() != records[name]['sha256'].upper():
|
||||
raise RuntimeError('Base ZIP hash mismatch: ' + name)
|
||||
destination = args.output.joinpath(*parts.parts)
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_bytes(data)
|
||||
overlays = {
|
||||
'd4r/_nvngx_gfx1101.dll': args.shim,
|
||||
'd4r/d4r.ini': repo / 'packaging/windows/d4r.ini',
|
||||
'WINDOWS_README.txt': repo / 'packaging/windows/WINDOWS_README.txt',
|
||||
'd4r/d4r-check.ps1': repo / 'scripts/windows/d4r-check.ps1',
|
||||
'install.bat': repo / 'packaging/windows/install.bat',
|
||||
'install-dlss.ps1': repo / 'scripts/windows/install-dlss.ps1',
|
||||
'd4r/dll-pins.txt': repo / 'tools/windows/dll-pins.txt',
|
||||
}
|
||||
for name, source in overlays.items():
|
||||
if name not in records:
|
||||
raise RuntimeError('Overlay is not part of the validated package layout: ' + name)
|
||||
shutil.copy2(source, args.output / name)
|
||||
records[name] = {'path': name, 'sha256': sha(source).upper(), 'version': None}
|
||||
prohibited = {'nvngx_dlss.dll', 'nvngx_dlssd.dll'}
|
||||
for path in args.output.rglob('*'):
|
||||
if path.is_file() and (path.name.lower() in prohibited or path.suffix.lower() in ('.spv', '.ptx', '.cubin', '.weights')):
|
||||
raise RuntimeError('Proprietary/model artifact cannot be bundled: ' + str(path))
|
||||
manifest.update(localDevelopment=True, packageWorkingTreeDirty=True, binarySourceCommit=None,
|
||||
packageCommit=subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=repo, text=True).strip(),
|
||||
files=list(records.values()),
|
||||
localRefresh={'baseZipSha256': base_hash, 'binaryProvenance': 'local modified build; hashes authoritative',
|
||||
'overlays': {name: sha(path) for name, path in overlays.items()},
|
||||
'rr': 'experimental synthetic validation; disabled by default; models generated separately'})
|
||||
manifest['targets']['gfx1101']['binarySourceCommit'] = None
|
||||
manifest['targets']['gfx1101']['hardwareValidation'] = 'SR prior limited evidence; native RR synthetic NGX lifecycle only; no game RR or performance parity'
|
||||
(args.output / 'd4r/package.json').write_text(json.dumps(manifest, indent=2) + '\n')
|
||||
args.archive.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(args.archive, 'x', zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
|
||||
for path in sorted(args.output.rglob('*')):
|
||||
if path.is_file():
|
||||
archive.write(path, path.relative_to(args.output).as_posix())
|
||||
with zipfile.ZipFile(args.archive) as archive:
|
||||
for name, entry in records.items():
|
||||
if hashlib.sha256(archive.read(name)).hexdigest().upper() != entry['sha256'].upper():
|
||||
raise RuntimeError('Refreshed ZIP hash mismatch: ' + name)
|
||||
report = {'zip': str(args.archive), 'sha256': sha(args.archive), 'bytes': args.archive.stat().st_size,
|
||||
'allHashesVerified': True, 'privateOverrideBundled': False, 'rrModelBundled': False,
|
||||
'sourceCommitCreated': False, 'published': False, 'baseZipSha256': base_hash}
|
||||
args.archive.with_suffix('.validation.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,326 @@
|
||||
#pragma once
|
||||
#include <d3d12.h>
|
||||
#include <wrl/client.h>
|
||||
#include "../../engine/runtime.h"
|
||||
#include "binary16.h"
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
// Canonical-input replay through the native D3D12/Vulkan shared packet. The
|
||||
// packet is GPU memory at the API boundary; CPU access is fixture upload/QA only.
|
||||
namespace rr_bridge {
|
||||
inline void check(VkResult r, const char* operation) {
|
||||
if (r != VK_SUCCESS) throw std::runtime_error(std::string(operation) + " VkResult=" + std::to_string(r));
|
||||
}
|
||||
inline std::vector<char> read(const std::filesystem::path& path) {
|
||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||
if (!f) throw std::runtime_error("Cannot read RR fixture: " + path.string());
|
||||
std::vector<char> result(size_t(f.tellg())); f.seekg(0); f.read(result.data(), std::streamsize(result.size()));
|
||||
if (!f) throw std::runtime_error("Incomplete RR fixture read");
|
||||
return result;
|
||||
}
|
||||
struct Frame { uint32_t id; d4r::RFrameSettings settings; std::array<bool, 4> optional{}; };
|
||||
struct Plane {
|
||||
const char* name; VkFormat format; uint32_t bytes;
|
||||
VkDeviceSize offset = 0, pitch = 0;
|
||||
bool enabled = true;
|
||||
};
|
||||
struct Layout {
|
||||
uint32_t rw = 0, rh = 0, ow = 0, oh = 0;
|
||||
std::array<Plane, 11> planes{{
|
||||
{"color", VK_FORMAT_R16G16B16A16_SFLOAT, 8}, {"motion", VK_FORMAT_R16G16_SFLOAT, 4},
|
||||
{"depth", VK_FORMAT_R32_SFLOAT, 4}, {"normals", VK_FORMAT_R16G16B16A16_SFLOAT, 8},
|
||||
{"roughness", VK_FORMAT_R16G16B16A16_SFLOAT, 8}, {"diffuse", VK_FORMAT_R8G8B8A8_UNORM, 4},
|
||||
{"specular", VK_FORMAT_R8G8B8A8_UNORM, 4},
|
||||
{"hitdistance", VK_FORMAT_R32_SFLOAT, 4, 0, 0, false},
|
||||
{"disocclusion", VK_FORMAT_R16_SFLOAT, 2, 0, 0, false},
|
||||
{"sss", VK_FORMAT_R16_SFLOAT, 2, 0, 0, false},
|
||||
{"particles", VK_FORMAT_R16G16B16A16_SFLOAT, 8, 0, 0, false}}};
|
||||
VkDeviceSize bytes = 0, outputOffset = 0, outputPitch = 0;
|
||||
std::filesystem::path inputs, output, reference;
|
||||
std::vector<Frame> frames;
|
||||
// Runtime layout construction performs no fixture I/O. Both callers use
|
||||
// the same aligned packet contract for native texture transfers.
|
||||
Layout(uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight)
|
||||
: rw(renderWidth), rh(renderHeight), ow(outputWidth), oh(outputHeight) {
|
||||
if (!rw || !rh || !ow || !oh || rw > 16384 || rh > 16384 || ow > 16384 || oh > 16384)
|
||||
throw std::runtime_error("Invalid RR packet geometry");
|
||||
initializePacket();
|
||||
}
|
||||
void initializePacket() {
|
||||
bytes = 0;
|
||||
for (auto& plane : planes) {
|
||||
bytes = (bytes + 511) & ~VkDeviceSize(511); plane.offset = bytes;
|
||||
plane.pitch = (VkDeviceSize(rw) * plane.bytes + 255) & ~VkDeviceSize(255);
|
||||
bytes += plane.pitch * rh;
|
||||
}
|
||||
outputOffset = (bytes + 511) & ~VkDeviceSize(511);
|
||||
outputPitch = (VkDeviceSize(ow) * 8 + 255) & ~VkDeviceSize(255);
|
||||
bytes = outputOffset + outputPitch * oh;
|
||||
}
|
||||
Layout(const std::string& inputDir, const std::string& outputDir, const std::string& referenceDir)
|
||||
: inputs(inputDir), output(outputDir), reference(referenceDir) {
|
||||
std::ifstream f(inputs / "frames.txt"); std::string token;
|
||||
if (!(f >> token >> rw >> rh >> ow >> oh) || token != "size" || rw < 32 || rh < 32 ||
|
||||
rw > 1024 || rh > 1024 || !ow || !oh || ow > 2560 || oh > 1440)
|
||||
throw std::runtime_error("Invalid bounded RR replay geometry");
|
||||
while (f >> token) {
|
||||
Frame frame{}; int reset, mode;
|
||||
auto& s = frame.settings;
|
||||
if (token != "frame" || !(f >> frame.id >> s.jitter[0] >> s.jitter[1] >> s.motionScale[0] >>
|
||||
s.motionScale[1] >> s.preExposure >> reset >> mode)) throw std::runtime_error("Invalid RR frame header");
|
||||
for (auto& value : s.worldToView) if (!(f >> value)) throw std::runtime_error("Invalid RR view matrix");
|
||||
for (auto& value : s.viewToClip) if (!(f >> value)) throw std::runtime_error("Invalid RR projection matrix");
|
||||
s.reset = reset != 0; s.depthInverted = (mode & 1) != 0; s.hardwareDepth = (mode & 2) != 0;
|
||||
s.roughnessPacked = (mode & 4) != 0;
|
||||
if (!std::isfinite(s.preExposure) || s.preExposure <= 0 ||
|
||||
!std::isfinite(s.jitter[0]) || !std::isfinite(s.jitter[1]) ||
|
||||
!std::isfinite(s.motionScale[0]) || !std::isfinite(s.motionScale[1]))
|
||||
throw std::runtime_error("Nonfinite or invalid RR frame settings");
|
||||
for (float value : s.worldToView) if (!std::isfinite(value)) throw std::runtime_error("Nonfinite RR view matrix");
|
||||
for (float value : s.viewToClip) if (!std::isfinite(value)) throw std::runtime_error("Nonfinite RR projection matrix");
|
||||
frames.push_back(frame);
|
||||
if (frames.size() > 32) throw std::runtime_error("RR replay is limited to 32 frames");
|
||||
}
|
||||
if (frames.empty()) throw std::runtime_error("RR fixture has no frames");
|
||||
std::set<uint32_t> ids;
|
||||
for (auto& frame : frames) {
|
||||
if (!ids.insert(frame.id).second) throw std::runtime_error("Duplicate RR frame ID");
|
||||
for (size_t i = 7; i < planes.size(); ++i) {
|
||||
frame.optional[i - 7] = std::filesystem::exists(inputs / (std::to_string(frame.id) + "-" + planes[i].name + ".bin"));
|
||||
planes[i].enabled = planes[i].enabled || frame.optional[i - 7];
|
||||
}
|
||||
}
|
||||
initializePacket();
|
||||
std::filesystem::create_directories(output);
|
||||
for (const auto& frame : frames)
|
||||
if (std::filesystem::exists(output / (std::to_string(frame.id) + "-out.bin")))
|
||||
throw std::runtime_error("Use a fresh RR output directory");
|
||||
}
|
||||
void upload(void* destination, unsigned frame) const {
|
||||
std::memset(destination, 0, size_t(bytes));
|
||||
for (const auto& p : planes) {
|
||||
if (!p.enabled) continue;
|
||||
const auto index = size_t(&p - planes.data());
|
||||
if (index >= 7 && !frames.at(frame).optional[index - 7]) continue;
|
||||
auto data = read(inputs / (std::to_string(frames.at(frame).id) + "-" + p.name + ".bin"));
|
||||
if (data.size() != size_t(rw) * rh * p.bytes) throw std::runtime_error("RR input size mismatch");
|
||||
for (uint32_t y = 0; y < rh; ++y)
|
||||
std::memcpy(static_cast<char*>(destination) + p.offset + y * p.pitch,
|
||||
data.data() + size_t(y) * rw * p.bytes, size_t(rw) * p.bytes);
|
||||
}
|
||||
}
|
||||
void validate(const void* source, unsigned frame) const {
|
||||
std::vector<char> packed(size_t(ow) * oh * 8);
|
||||
for (uint32_t y = 0; y < oh; ++y)
|
||||
std::memcpy(packed.data() + size_t(y) * ow * 8,
|
||||
static_cast<const char*>(source) + outputOffset + y * outputPitch, size_t(ow) * 8);
|
||||
double low = std::numeric_limits<double>::infinity(), high = -low;
|
||||
for (size_t i = 0; i < packed.size() / 2; ++i) {
|
||||
uint16_t bits; std::memcpy(&bits, packed.data() + i * 2, 2);
|
||||
const double value = d4r::diag::binary16::half_value(bits);
|
||||
if (!std::isfinite(value)) throw std::runtime_error("RR output contains NaN/Inf");
|
||||
if (i % 4 != 3) { low = std::min(low, value); high = std::max(high, value); }
|
||||
}
|
||||
if (!(high - low > 1e-5)) throw std::runtime_error("RR output is uniform");
|
||||
const auto name = std::to_string(frames.at(frame).id) + "-out.bin";
|
||||
if (!reference.empty() && read(reference / name) != packed)
|
||||
throw std::runtime_error("Bridged RR output differs from standalone replay");
|
||||
std::ofstream file(output / name, std::ios::binary); file.write(packed.data(), std::streamsize(packed.size()));
|
||||
if (!file) throw std::runtime_error("RR output write failed");
|
||||
std::printf("RR_BRIDGED_FRAME id=%u finite=1 nonuniform=1 standalone_exact=%u output=%ux%u\n",
|
||||
frames[frame].id, !reference.empty(), ow, oh);
|
||||
}
|
||||
};
|
||||
struct D3D12Frames {
|
||||
const Layout& layout;
|
||||
std::array<Microsoft::WRL::ComPtr<ID3D12Resource>, 11> inputs;
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> output;
|
||||
static void checkD3D(HRESULT r) { if (FAILED(r)) throw std::runtime_error("RR D3D12 texture operation failed: " + std::to_string(r)); }
|
||||
static DXGI_FORMAT format(VkFormat f) {
|
||||
switch (f) {
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
case VK_FORMAT_R16G16_SFLOAT: return DXGI_FORMAT_R16G16_FLOAT;
|
||||
case VK_FORMAT_R32_SFLOAT: return DXGI_FORMAT_R32_FLOAT;
|
||||
case VK_FORMAT_R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
case VK_FORMAT_R32G32_SFLOAT: return DXGI_FORMAT_R32G32_FLOAT;
|
||||
case VK_FORMAT_R16_SFLOAT: return DXGI_FORMAT_R16_FLOAT;
|
||||
case VK_FORMAT_R8_UNORM: return DXGI_FORMAT_R8_UNORM;
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||
default: throw std::runtime_error("Unsupported canonical RR D3D12 format");
|
||||
}
|
||||
}
|
||||
static void barrier(ID3D12GraphicsCommandList* cb, ID3D12Resource* image, D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) {
|
||||
D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
b.Transition = {image, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, from, to}; cb->ResourceBarrier(1, &b);
|
||||
}
|
||||
static D3D12_TEXTURE_COPY_LOCATION placed(ID3D12Resource* resource, VkDeviceSize offset, VkDeviceSize pitch,
|
||||
DXGI_FORMAT f, uint32_t width, uint32_t height) {
|
||||
D3D12_TEXTURE_COPY_LOCATION result{}; result.pResource = resource;
|
||||
result.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; result.PlacedFootprint.Offset = offset;
|
||||
result.PlacedFootprint.Footprint = {f, width, height, 1, UINT(pitch)}; return result;
|
||||
}
|
||||
static D3D12_TEXTURE_COPY_LOCATION texture(ID3D12Resource* resource) {
|
||||
D3D12_TEXTURE_COPY_LOCATION result{}; result.pResource = resource;
|
||||
result.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; return result;
|
||||
}
|
||||
D3D12Frames(const Layout& l, ID3D12Device* device, DXGI_FORMAT depthFormat = DXGI_FORMAT_UNKNOWN) : layout(l) {
|
||||
auto create = [&](DXGI_FORMAT f, uint32_t width, uint32_t height, bool isOutput, ID3D12Resource** resource) {
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Format = f;
|
||||
desc.Width = width; desc.Height = height; desc.DepthOrArraySize = desc.MipLevels = 1; desc.SampleDesc.Count = 1;
|
||||
if (isOutput) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
if (f == DXGI_FORMAT_D32_FLOAT || f == DXGI_FORMAT_R32G8X24_TYPELESS || f == DXGI_FORMAT_D32_FLOAT_S8X24_UINT)
|
||||
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
||||
checkD3D(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc,
|
||||
isOutput ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
|
||||
nullptr, IID_PPV_ARGS(resource)));
|
||||
};
|
||||
for (size_t i = 0; i < inputs.size(); ++i) if (l.planes[i].enabled)
|
||||
create(i == 2 && depthFormat != DXGI_FORMAT_UNKNOWN ? depthFormat : format(l.planes[i].format), l.rw, l.rh, false, inputs[i].GetAddressOf());
|
||||
create(DXGI_FORMAT_R16G16B16A16_FLOAT, l.ow, l.oh, true, output.GetAddressOf());
|
||||
}
|
||||
void recordInputs(ID3D12GraphicsCommandList* cb, ID3D12Resource* upload, ID3D12Resource* shared, uint32_t optionalMask = 15) {
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
if (!inputs[i]) continue;
|
||||
if (i >= 7 && !(optionalMask & (1u << (i - 7)))) continue;
|
||||
auto* input = inputs[i].Get(); const auto& plane = layout.planes[i];
|
||||
auto copyFormat = format(plane.format);
|
||||
if (i == 2) {
|
||||
Microsoft::WRL::ComPtr<ID3D12Device> device; checkD3D(input->GetDevice(IID_PPV_ARGS(&device)));
|
||||
D3D12_RESOURCE_DESC desc{};
|
||||
#if defined(__MINGW32__)
|
||||
input->GetDesc(&desc);
|
||||
#else
|
||||
desc = input->GetDesc();
|
||||
#endif
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
|
||||
device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, nullptr, nullptr, nullptr);
|
||||
copyFormat = footprint.Footprint.Format;
|
||||
}
|
||||
auto src = placed(upload, plane.offset, plane.pitch, copyFormat, layout.rw, layout.rh);
|
||||
auto image = texture(input);
|
||||
barrier(cb, input, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
cb->CopyTextureRegion(&image, 0, 0, 0, &src, nullptr);
|
||||
barrier(cb, input, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
auto dst = placed(shared, plane.offset, plane.pitch, copyFormat, layout.rw, layout.rh);
|
||||
cb->CopyTextureRegion(&dst, 0, 0, 0, &image, nullptr);
|
||||
barrier(cb, input, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
}
|
||||
}
|
||||
void recordOutput(ID3D12GraphicsCommandList* cb, ID3D12Resource* shared, ID3D12Resource* readback) {
|
||||
auto src = placed(shared, layout.outputOffset, layout.outputPitch, DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
|
||||
auto image = texture(output.Get());
|
||||
barrier(cb, output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
cb->CopyTextureRegion(&image, 0, 0, 0, &src, nullptr);
|
||||
barrier(cb, output.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
auto dst = placed(readback, layout.outputOffset, layout.outputPitch, DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
|
||||
cb->CopyTextureRegion(&dst, 0, 0, 0, &image, nullptr);
|
||||
barrier(cb, output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
}
|
||||
};
|
||||
struct Image {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED; bool initialized = false;
|
||||
VkDevice device; VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE;
|
||||
~Image() { if (view) vkDestroyImageView(device, view, nullptr); if (image) vkDestroyImage(device, image, nullptr); if (memory) vkFreeMemory(device, memory, nullptr); }
|
||||
};
|
||||
struct Replay {
|
||||
const Layout& layout;
|
||||
VkPhysicalDevice physical; VkDevice device;
|
||||
std::array<std::unique_ptr<Image>, 11> images;
|
||||
std::array<std::vector<std::unique_ptr<Image>>, 11> alternatives;
|
||||
std::unique_ptr<d4r::RModel> model;
|
||||
std::unique_ptr<d4r::REngine> engine;
|
||||
bool first = true;
|
||||
Replay(const Layout& l, VkPhysicalDevice physical, VkDevice device, const std::string& directory)
|
||||
: layout(l), physical(physical), device(device) {
|
||||
const auto data = read(std::filesystem::path(directory) / "rr.txt");
|
||||
const std::string manifest(data.begin(), data.end());
|
||||
if (manifest.rfind("D4RR0001", 0) != 0 || manifest.find("arithmetic gfx11") == std::string::npos)
|
||||
throw std::runtime_error("This canonical RR bridge gate requires a D/E gfx11 model");
|
||||
for (size_t i = 0; i < images.size(); ++i) if (l.planes[i].enabled) configure(i, l.planes[i].format);
|
||||
model = std::make_unique<d4r::RModel>(physical, device, directory);
|
||||
engine = std::make_unique<d4r::REngine>(*model, l.rw, l.rh, l.ow, l.oh);
|
||||
}
|
||||
// Cache views by format: REngine caches descriptors referencing old views,
|
||||
// so they must remain alive until the engine is destroyed.
|
||||
void configure(size_t i, VkFormat format) {
|
||||
if (images.at(i) && images[i]->format == format) return;
|
||||
for (auto& previous : alternatives[i]) if (previous->format == format) {
|
||||
images[i].swap(previous); return;
|
||||
}
|
||||
VkFormatProperties properties{}; vkGetPhysicalDeviceFormatProperties(physical, format, &properties);
|
||||
const auto required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
|
||||
if ((properties.optimalTilingFeatures & required) != required)
|
||||
throw std::runtime_error("RR input format lacks Vulkan sampled/transfer support");
|
||||
VkPhysicalDeviceMemoryProperties memory; vkGetPhysicalDeviceMemoryProperties(physical, &memory);
|
||||
auto image = std::make_unique<Image>(); image->device = device; image->format = format;
|
||||
VkImageCreateInfo ci{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; ci.imageType = VK_IMAGE_TYPE_2D;
|
||||
ci.format = format; ci.extent = {layout.rw, layout.rh, 1}; ci.mipLevels = ci.arrayLayers = 1;
|
||||
ci.samples = VK_SAMPLE_COUNT_1_BIT; ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
check(vkCreateImage(device, &ci, nullptr, &image->image), "RR input image");
|
||||
VkMemoryRequirements req; vkGetImageMemoryRequirements(device, image->image, &req);
|
||||
uint32_t type = memory.memoryTypeCount;
|
||||
for (uint32_t n = 0; n < memory.memoryTypeCount; ++n)
|
||||
if ((req.memoryTypeBits & (1u << n)) && (memory.memoryTypes[n].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { type = n; break; }
|
||||
if (type == memory.memoryTypeCount) throw std::runtime_error("No RR image memory type");
|
||||
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; ai.allocationSize = req.size; ai.memoryTypeIndex = type;
|
||||
check(vkAllocateMemory(device, &ai, nullptr, &image->memory), "RR image memory");
|
||||
check(vkBindImageMemory(device, image->image, image->memory, 0), "Bind RR image");
|
||||
VkImageViewCreateInfo vi{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; vi.image = image->image;
|
||||
vi.viewType = VK_IMAGE_VIEW_TYPE_2D; vi.format = ci.format; vi.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
check(vkCreateImageView(device, &vi, nullptr, &image->view), "RR input view");
|
||||
if (images[i]) alternatives[i].push_back(std::move(images[i]));
|
||||
images[i] = std::move(image);
|
||||
}
|
||||
void record(VkCommandBuffer cb, VkBuffer shared, unsigned frame) {
|
||||
uint32_t mask = 0;
|
||||
for (unsigned i = 0; i < 4; ++i) if (layout.frames.at(frame).optional[i]) mask |= 1u << i;
|
||||
record(cb, shared, layout.frames.at(frame).settings, mask);
|
||||
}
|
||||
// Own settings at the NGX submission boundary; never retain game pointers.
|
||||
void record(VkCommandBuffer cb, VkBuffer shared, const d4r::RFrameSettings& settings, uint32_t optionalMask = 15) {
|
||||
for (size_t i = 0; i < images.size(); ++i) {
|
||||
if (!layout.planes[i].enabled) continue;
|
||||
if (i >= 7 && !(optionalMask & (1u << (i - 7)))) continue;
|
||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; b.image = images[i]->image;
|
||||
b.oldLayout = !images[i]->initialized ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
b.srcAccessMask = !images[i]->initialized ? 0 : VK_ACCESS_SHADER_READ_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
const auto& plane = layout.planes[i];
|
||||
VkBufferImageCopy copy{}; copy.bufferOffset = plane.offset; copy.bufferRowLength = uint32_t(plane.pitch / plane.bytes);
|
||||
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; copy.imageExtent = {layout.rw, layout.rh, 1};
|
||||
vkCmdCopyBufferToImage(cb, shared, images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
|
||||
images[i]->initialized = true;
|
||||
b.oldLayout = b.newLayout; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
}
|
||||
d4r::RFrameImages inputs{}; inputs.color = images[0]->view; inputs.motion = images[1]->view; inputs.depth = images[2]->view;
|
||||
inputs.normals = images[3]->view; inputs.roughness = images[4]->view; inputs.diffuseAlbedo = images[5]->view; inputs.specularAlbedo = images[6]->view;
|
||||
VkImageView* optional[] = {&inputs.specularHitDistance, &inputs.disocclusionMask, &inputs.subsurfaceGuide, &inputs.colorBeforeParticles};
|
||||
for (size_t i = 7; i < images.size(); ++i) if (layout.planes[i].enabled && (optionalMask & (1u << (i - 7)))) *optional[i - 7] = images[i]->view;
|
||||
engine->setFrame(inputs, settings);
|
||||
if (first) { model->recordUpload(cb); engine->recordInitialize(cb); first = false; }
|
||||
engine->recordFrame(cb);
|
||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; b.image = engine->outputImage();
|
||||
b.oldLayout = b.newLayout = VK_IMAGE_LAYOUT_GENERAL; b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; b.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
VkBufferImageCopy copy{}; copy.bufferOffset = layout.outputOffset; copy.bufferRowLength = uint32_t(layout.outputPitch / 8);
|
||||
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; copy.imageExtent = {layout.ow, layout.oh, 1};
|
||||
vkCmdCopyImageToBuffer(cb, engine->outputImage(), VK_IMAGE_LAYOUT_GENERAL, shared, 1, ©);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Native D3D12/Vulkan prerequisite query for the RR engine adapter. No dispatch.
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_4.h>
|
||||
#include <volk.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
IDXGIFactory4* factory = nullptr;
|
||||
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) return 1;
|
||||
IDXGIAdapter1* selected = nullptr;
|
||||
DXGI_ADAPTER_DESC1 adapter{};
|
||||
for (UINT i = 0;; ++i) {
|
||||
IDXGIAdapter1* next = nullptr;
|
||||
if (factory->EnumAdapters1(i, &next) == DXGI_ERROR_NOT_FOUND) break;
|
||||
if (!next) break;
|
||||
DXGI_ADAPTER_DESC1 desc{};
|
||||
next->GetDesc1(&desc);
|
||||
if (desc.VendorId == 0x1002 && !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) {
|
||||
selected = next; adapter = desc; break;
|
||||
}
|
||||
next->Release();
|
||||
}
|
||||
factory->Release();
|
||||
if (!selected) { std::puts("FAIL no native AMD D3D12 adapter"); return 1; }
|
||||
ID3D12Device* device = nullptr;
|
||||
const HRESULT created = D3D12CreateDevice(selected, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device));
|
||||
selected->Release();
|
||||
if (FAILED(created)) { std::puts("FAIL D3D12 device creation"); return 1; }
|
||||
device->Release();
|
||||
if (volkInitialize() != VK_SUCCESS) return 1;
|
||||
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = VK_API_VERSION_1_3;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &app;
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
if (vkCreateInstance(&info, nullptr, &instance) != VK_SUCCESS) return 1;
|
||||
volkLoadInstance(instance);
|
||||
uint32_t count = 0;
|
||||
vkEnumeratePhysicalDevices(instance, &count, nullptr);
|
||||
std::vector<VkPhysicalDevice> devices(count);
|
||||
vkEnumeratePhysicalDevices(instance, &count, devices.data());
|
||||
bool ready = false;
|
||||
for (auto physical : devices) {
|
||||
VkPhysicalDeviceIDProperties id{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id};
|
||||
vkGetPhysicalDeviceProperties2(physical, &properties);
|
||||
if (!id.deviceLUIDValid || std::memcmp(id.deviceLUID, &adapter.AdapterLuid, VK_LUID_SIZE)) continue;
|
||||
uint32_t extensionCount = 0;
|
||||
vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, nullptr);
|
||||
std::vector<VkExtensionProperties> extensions(extensionCount);
|
||||
vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, extensions.data());
|
||||
auto has = [&](const char* name) {
|
||||
return std::any_of(extensions.begin(), extensions.end(), [&](const auto& e) { return !std::strcmp(name, e.extensionName); });
|
||||
};
|
||||
VkPhysicalDeviceExternalBufferInfo buffer{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO};
|
||||
buffer.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
buffer.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT;
|
||||
VkExternalBufferProperties memory{VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES};
|
||||
vkGetPhysicalDeviceExternalBufferProperties(physical, &buffer, &memory);
|
||||
VkPhysicalDeviceExternalSemaphoreInfo semaphore{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO};
|
||||
semaphore.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT;
|
||||
VkExternalSemaphoreProperties fence{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES};
|
||||
vkGetPhysicalDeviceExternalSemaphoreProperties(physical, &semaphore, &fence);
|
||||
const bool memoryImport = (memory.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) != 0;
|
||||
const bool fenceImport = (fence.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT) != 0;
|
||||
const bool memoryExtension = has("VK_KHR_external_memory_win32");
|
||||
const bool fenceExtension = has("VK_KHR_external_semaphore_win32");
|
||||
std::printf("RR_INTEROP device=%s luid_match=1 memory_win32=%u d3d12_buffer_import=%u dedicated_only=%u semaphore_win32=%u d3d12_fence_import=%u\n",
|
||||
properties.properties.deviceName, memoryExtension, memoryImport,
|
||||
unsigned((memory.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT) != 0),
|
||||
fenceExtension, fenceImport);
|
||||
ready = memoryExtension && memoryImport && fenceExtension && fenceImport;
|
||||
}
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
std::puts(ready ? "PASS RR_INTEROP_CAPABILITIES import_or_dispatch_executed=0" : "FAIL RR_INTEROP_CAPABILITIES");
|
||||
return ready ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// D3D12 -> Vulkan -> D3D12 shared-buffer/fence gate for native RR integration.
|
||||
// Synthetic transfers and optional local RR model replay. No NGX, CUDA, or game.
|
||||
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_4.h>
|
||||
#include <wrl/client.h>
|
||||
#include <volk.h>
|
||||
#include <vulkan/vulkan_win32.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "rr_bridge_replay.h"
|
||||
#include "rr_native_packet.h"
|
||||
using Microsoft::WRL::ComPtr;
|
||||
namespace {
|
||||
void dx(HRESULT value, const char* operation) {
|
||||
if (FAILED(value)) throw std::runtime_error(std::string(operation) + " HRESULT=" + std::to_string(value));
|
||||
}
|
||||
void vk(VkResult value, const char* operation) {
|
||||
if (value != VK_SUCCESS) throw std::runtime_error(std::string(operation) + " VkResult=" + std::to_string(value));
|
||||
}
|
||||
struct Handle { HANDLE value = nullptr; ~Handle() { if (value) CloseHandle(value); } };
|
||||
ComPtr<ID3D12Resource> buffer(ID3D12Device* device, UINT64 bytes, D3D12_HEAP_TYPE type,
|
||||
D3D12_HEAP_FLAGS flags, D3D12_RESOURCE_STATES state) {
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = type;
|
||||
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = bytes; desc.Height = desc.DepthOrArraySize = desc.MipLevels = 1;
|
||||
desc.SampleDesc.Count = 1; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
if (type == D3D12_HEAP_TYPE_DEFAULT) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
ComPtr<ID3D12Resource> result;
|
||||
dx(device->CreateCommittedResource(&heap, flags, &desc, state, nullptr, IID_PPV_ARGS(&result)), "Create buffer");
|
||||
return result;
|
||||
}
|
||||
void transition(ID3D12GraphicsCommandList* list, ID3D12Resource* resource,
|
||||
D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) {
|
||||
D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
b.Transition = {resource, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, from, to};
|
||||
list->ResourceBarrier(1, &b);
|
||||
}
|
||||
struct Vulkan {
|
||||
std::unique_ptr<rr_bridge::NativeDevice> native;
|
||||
std::unique_ptr<rr_bridge::NativePacket> packet;
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkBuffer shared = VK_NULL_HANDLE, readback = VK_NULL_HANDLE;
|
||||
VkDeviceMemory host = VK_NULL_HANDLE;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
|
||||
VkSemaphore semaphore = VK_NULL_HANDLE;
|
||||
VkCommandPool pool = VK_NULL_HANDLE;
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
bool submitted = false;
|
||||
std::unique_ptr<rr_bridge::Replay> replay;
|
||||
~Vulkan() {
|
||||
// The outer process timeout also bounds a stalled driver call. Never
|
||||
// destroy resources that a failed/timed-out submission might still use.
|
||||
if (submitted) { (void)replay.release(); (void)packet.release(); (void)native.release(); return; }
|
||||
replay.reset();
|
||||
if (device) {
|
||||
if (fence) vkDestroyFence(device, fence, nullptr);
|
||||
if (pool) vkDestroyCommandPool(device, pool, nullptr);
|
||||
|
||||
|
||||
if (readback) vkDestroyBuffer(device, readback, nullptr);
|
||||
if (image) vkDestroyImage(device, image, nullptr);
|
||||
|
||||
if (host) vkFreeMemory(device, host, nullptr);
|
||||
if (imageMemory) vkFreeMemory(device, imageMemory, nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
int main(int argc, char** argv) try {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||
UINT64 bytes = 1u << 20;
|
||||
unsigned iterations = 8;
|
||||
uint32_t imageWidth = 0, imageHeight = 0;
|
||||
std::string rrModel, rrInputs, rrOutput, rrReference;
|
||||
for (int i = 1; i < argc; i += 2) {
|
||||
if (i + 1 == argc) throw std::runtime_error("Missing argument value");
|
||||
const std::string key = argv[i];
|
||||
size_t end = 0;
|
||||
const std::string text = argv[i + 1];
|
||||
if (key == "--rr-model") { rrModel = text; continue; }
|
||||
if (key == "--rr-inputs") { rrInputs = text; continue; }
|
||||
if (key == "--rr-output") { rrOutput = text; continue; }
|
||||
if (key == "--rr-reference") { rrReference = text; continue; }
|
||||
if (text.empty() || text[0] == '-') throw std::runtime_error("Expected positive argument");
|
||||
const auto value = std::stoull(text, &end);
|
||||
if (end != text.size()) throw std::runtime_error("Invalid numeric argument");
|
||||
if (key == "--bytes") bytes = value;
|
||||
else if (key == "--iterations" && value <= 32) iterations = unsigned(value);
|
||||
else if (key == "--image-width" && value <= 8192) imageWidth = uint32_t(value);
|
||||
else if (key == "--image-height" && value <= 8192) imageHeight = uint32_t(value);
|
||||
else throw std::runtime_error("Unknown or out-of-range argument");
|
||||
}
|
||||
std::unique_ptr<rr_bridge::Layout> rrLayout;
|
||||
if (!rrModel.empty()) {
|
||||
if (rrInputs.empty() || rrOutput.empty() || imageWidth || imageHeight) throw std::runtime_error("RR replay needs model/inputs/output and no image-clear mode");
|
||||
rrLayout = std::make_unique<rr_bridge::Layout>(rrInputs, rrOutput, rrReference);
|
||||
bytes = rrLayout->bytes; iterations = unsigned(rrLayout->frames.size());
|
||||
} else if (!rrInputs.empty() || !rrOutput.empty() || !rrReference.empty()) throw std::runtime_error("Missing RR model");
|
||||
if (!bytes || bytes % 4 || bytes > (64u << 20) || !iterations)
|
||||
throw std::runtime_error("Need a 4-byte-aligned buffer (4 bytes..64MiB) and 1..32 iterations");
|
||||
const UINT64 rowPitch = (UINT64(imageWidth) * 8 + 255) & ~UINT64(255);
|
||||
const bool imageMode = imageWidth && imageHeight;
|
||||
if (bool(imageWidth) != bool(imageHeight) || (imageMode && bytes != rowPitch * imageHeight))
|
||||
throw std::runtime_error("Image dimensions must match the 256-byte-padded RGBA16F buffer size");
|
||||
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "DXGI factory");
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
DXGI_ADAPTER_DESC1 description{};
|
||||
bool selected = false;
|
||||
for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) {
|
||||
dx(adapter->GetDesc1(&description), "DXGI adapter description");
|
||||
if (description.VendorId == 0x1002 && !(description.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { selected = true; break; }
|
||||
adapter.Reset();
|
||||
}
|
||||
if (!selected) throw std::runtime_error("No AMD D3D12 adapter");
|
||||
ComPtr<ID3D12Device> d3d; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&d3d)), "D3D12 device");
|
||||
ComPtr<ID3D12CommandQueue> queue;
|
||||
D3D12_COMMAND_QUEUE_DESC q{}; q.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
dx(d3d->CreateCommandQueue(&q, IID_PPV_ARGS(&queue)), "D3D12 queue");
|
||||
auto shared = buffer(d3d.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
|
||||
auto upload = buffer(d3d.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
auto readback = buffer(d3d.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
std::unique_ptr<rr_bridge::D3D12Frames> d3dFrames;
|
||||
if (rrLayout) d3dFrames = std::make_unique<rr_bridge::D3D12Frames>(*rrLayout, d3d.Get());
|
||||
ComPtr<ID3D12Fence> d3dFence; dx(d3d->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&d3dFence)), "Shared D3D12 fence");
|
||||
Handle memoryHandle, fenceHandle, event;
|
||||
dx(d3d->CreateSharedHandle(shared.Get(), nullptr, GENERIC_ALL, nullptr, &memoryHandle.value), "Shared buffer handle");
|
||||
dx(d3d->CreateSharedHandle(d3dFence.Get(), nullptr, GENERIC_ALL, nullptr, &fenceHandle.value), "Shared fence handle");
|
||||
event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!event.value) throw std::runtime_error("Fence event creation failed");
|
||||
Vulkan v;
|
||||
v.native = std::make_unique<rr_bridge::NativeDevice>();
|
||||
v.native->initialize(description.AdapterLuid, bool(rrLayout));
|
||||
v.device = v.native->device;
|
||||
const auto physical = v.native->physical;
|
||||
const auto family = v.native->family;
|
||||
const auto vkQueue = v.native->queue;
|
||||
if (rrLayout) v.replay = std::make_unique<rr_bridge::Replay>(*rrLayout, physical, v.device, rrModel);
|
||||
v.packet = std::make_unique<rr_bridge::NativePacket>(*v.native);
|
||||
v.packet->initialize(bytes, memoryHandle.value, fenceHandle.value);
|
||||
v.shared = v.packet->buffer; v.semaphore = v.packet->semaphore;
|
||||
VkPhysicalDeviceMemoryProperties memory{}; vkGetPhysicalDeviceMemoryProperties(physical, &memory);
|
||||
auto memoryType = [&](uint32_t bits, VkMemoryPropertyFlags flags) {
|
||||
for (uint32_t i = 0; i < memory.memoryTypeCount; ++i)
|
||||
if ((bits & (1u << i)) && (memory.memoryTypes[i].propertyFlags & flags) == flags) return i;
|
||||
throw std::runtime_error("No compatible Vulkan memory type");
|
||||
};
|
||||
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; bi.size = bytes;
|
||||
bi.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
VkMemoryRequirements req{};
|
||||
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
vk(vkCreateBuffer(v.device, &bi, nullptr, &v.readback), "Vulkan readback buffer");
|
||||
vkGetBufferMemoryRequirements(v.device, v.readback, &req);
|
||||
ai.pNext = nullptr; ai.allocationSize = req.size;
|
||||
ai.memoryTypeIndex = memoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
vk(vkAllocateMemory(v.device, &ai, nullptr, &v.host), "Vulkan readback memory");
|
||||
vk(vkBindBufferMemory(v.device, v.readback, v.host, 0), "Bind readback");
|
||||
if (imageMode) {
|
||||
VkImageCreateInfo image{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
image.imageType = VK_IMAGE_TYPE_2D; image.format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
image.extent = {imageWidth, imageHeight, 1}; image.mipLevels = image.arrayLayers = 1;
|
||||
image.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
vk(vkCreateImage(v.device, &image, nullptr, &v.image), "Canonical RR image");
|
||||
vkGetImageMemoryRequirements(v.device, v.image, &req);
|
||||
ai.allocationSize = req.size; ai.memoryTypeIndex = memoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
vk(vkAllocateMemory(v.device, &ai, nullptr, &v.imageMemory), "Canonical RR image memory");
|
||||
vk(vkBindImageMemory(v.device, v.image, v.imageMemory, 0), "Bind canonical image");
|
||||
}
|
||||
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; pci.queueFamilyIndex = family;
|
||||
vk(vkCreateCommandPool(v.device, &pci, nullptr, &v.pool), "Vulkan command pool");
|
||||
VkCommandBufferAllocateInfo cai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; cai.commandPool = v.pool; cai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cai.commandBufferCount = 1;
|
||||
VkCommandBuffer cb; vk(vkAllocateCommandBuffers(v.device, &cai, &cb), "Vulkan command buffer");
|
||||
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; vk(vkCreateFence(v.device, &fci, nullptr, &v.fence), "Vulkan completion fence");
|
||||
std::puts("RR_INTEROP_IMPORTED memory=dedicated_d3d12_resource semaphore=d3d12_timeline_fence luid_match=1");
|
||||
ComPtr<ID3D12CommandAllocator> producerAllocator, consumerAllocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> producer, consumer;
|
||||
dx(d3d->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&producerAllocator)), "Producer allocator");
|
||||
dx(d3d->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&consumerAllocator)), "Consumer allocator");
|
||||
dx(d3d->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, producerAllocator.Get(), nullptr, IID_PPV_ARGS(&producer)), "Producer list");
|
||||
dx(d3d->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, consumerAllocator.Get(), nullptr, IID_PPV_ARGS(&consumer)), "Consumer list");
|
||||
dx(producer->Close(), "Initial producer close"); dx(consumer->Close(), "Initial consumer close");
|
||||
auto pattern = [](unsigned iteration, uint32_t word) { return (word * 2654435761u) ^ (0x13579bdfu + iteration * 97u); };
|
||||
for (unsigned iteration = 0; iteration < iterations; ++iteration) {
|
||||
uint32_t* input = nullptr; D3D12_RANGE noRead{};
|
||||
dx(upload->Map(0, &noRead, reinterpret_cast<void**>(&input)), "Upload map");
|
||||
if (rrLayout) rrLayout->upload(input, iteration);
|
||||
else for (uint32_t word = 0; word < bytes / 4; ++word) input[word] = pattern(iteration, word);
|
||||
upload->Unmap(0, nullptr);
|
||||
dx(producerAllocator->Reset(), "Producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "Producer reset");
|
||||
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
if (d3dFrames) d3dFrames->recordInputs(producer.Get(), upload.Get(), shared.Get());
|
||||
else producer->CopyBufferRegion(shared.Get(), 0, upload.Get(), 0, bytes);
|
||||
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
|
||||
dx(producer->Close(), "Producer close"); ID3D12CommandList* p[] = {producer.Get()}; queue->ExecuteCommandLists(1, p);
|
||||
const uint64_t ready = iteration * 3 + 1, done = ready + 1, complete = ready + 2;
|
||||
dx(queue->Signal(d3dFence.Get(), ready), "D3D12 producer signal");
|
||||
vk(vkResetCommandPool(v.device, v.pool, 0), "Vulkan pool reset");
|
||||
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; vk(vkBeginCommandBuffer(cb, &begin), "Vulkan begin");
|
||||
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = v.shared; acquire.size = VK_WHOLE_SIZE;
|
||||
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = family; acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
if (rrLayout) acquire.dstAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
|
||||
VkBufferImageCopy imageRegion{};
|
||||
imageRegion.bufferRowLength = uint32_t(rowPitch / 8);
|
||||
imageRegion.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
imageRegion.imageExtent = {imageWidth, imageHeight, 1};
|
||||
auto imageBarrier = [&](VkImageLayout from, VkImageLayout to, VkAccessFlags source, VkAccessFlags destination) {
|
||||
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
b.image = v.image; b.oldLayout = from; b.newLayout = to;
|
||||
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; b.srcAccessMask = source; b.dstAccessMask = destination;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||
};
|
||||
if (rrLayout) {
|
||||
v.replay->record(cb, v.shared, iteration);
|
||||
} else if (imageMode) {
|
||||
vkCmdFillBuffer(cb, v.readback, 0, bytes, 0xdeadbeefu);
|
||||
VkBufferMemoryBarrier fill{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; fill.buffer = v.readback; fill.size = VK_WHOLE_SIZE;
|
||||
fill.srcQueueFamilyIndex = fill.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
fill.srcAccessMask = fill.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &fill, 0, nullptr);
|
||||
imageBarrier(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
vkCmdCopyBufferToImage(cb, v.shared, v.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageRegion);
|
||||
imageBarrier(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
vkCmdCopyImageToBuffer(cb, v.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, v.readback, 1, &imageRegion);
|
||||
imageBarrier(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
VkClearColorValue clear{}; clear.float32[0] = iteration % 2 ? .5f : .25f;
|
||||
clear.float32[1] = .5f; clear.float32[2] = .75f; clear.float32[3] = 1.f;
|
||||
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCmdClearColorImage(cb, v.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear, 1, &range);
|
||||
imageBarrier(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
} else {
|
||||
VkBufferCopy region{0, 0, bytes}; vkCmdCopyBuffer(cb, v.shared, v.readback, 1, ®ion);
|
||||
}
|
||||
VkBufferMemoryBarrier afterRead{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; afterRead.buffer = v.shared; afterRead.size = VK_WHOLE_SIZE;
|
||||
afterRead.srcQueueFamilyIndex = afterRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
afterRead.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; afterRead.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
if (!rrLayout) vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &afterRead, 0, nullptr);
|
||||
const uint32_t output = 0xa5a50000u | iteration;
|
||||
if (!rrLayout) vkCmdFillBuffer(cb, v.shared, 0, bytes, output);
|
||||
if (imageMode) {
|
||||
afterRead.srcAccessMask = afterRead.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &afterRead, 0, nullptr);
|
||||
vkCmdCopyImageToBuffer(cb, v.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, v.shared, 1, &imageRegion);
|
||||
}
|
||||
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = family; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
|
||||
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
|
||||
VkBufferMemoryBarrier hostRead{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; hostRead.buffer = v.readback; hostRead.size = VK_WHOLE_SIZE;
|
||||
hostRead.srcQueueFamilyIndex = hostRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
hostRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; hostRead.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1, &hostRead, 0, nullptr);
|
||||
vk(vkEndCommandBuffer(cb), "Vulkan end");
|
||||
VkTimelineSemaphoreSubmitInfo values{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO};
|
||||
values.waitSemaphoreValueCount = values.signalSemaphoreValueCount = 1; values.pWaitSemaphoreValues = &ready; values.pSignalSemaphoreValues = &done;
|
||||
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO, &values}; submit.waitSemaphoreCount = submit.signalSemaphoreCount = 1;
|
||||
submit.pWaitSemaphores = submit.pSignalSemaphores = &v.semaphore; submit.pWaitDstStageMask = &waitStage; submit.commandBufferCount = 1; submit.pCommandBuffers = &cb;
|
||||
vk(vkResetFences(v.device, 1, &v.fence), "Vulkan fence reset");
|
||||
vk(vkQueueSubmit(vkQueue, 1, &submit, v.fence), "Vulkan shared-buffer submission"); v.submitted = true;
|
||||
dx(queue->Wait(d3dFence.Get(), done), "D3D12 wait for Vulkan");
|
||||
dx(consumerAllocator->Reset(), "Consumer allocator reset"); dx(consumer->Reset(consumerAllocator.Get(), nullptr), "Consumer reset");
|
||||
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
if (d3dFrames) d3dFrames->recordOutput(consumer.Get(), shared.Get(), readback.Get());
|
||||
else consumer->CopyBufferRegion(readback.Get(), 0, shared.Get(), 0, bytes);
|
||||
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON);
|
||||
dx(consumer->Close(), "Consumer close"); ID3D12CommandList* c[] = {consumer.Get()}; queue->ExecuteCommandLists(1, c);
|
||||
dx(queue->Signal(d3dFence.Get(), complete), "D3D12 consumer signal");
|
||||
dx(d3dFence->SetEventOnCompletion(complete, event.value), "Completion event");
|
||||
if (WaitForSingleObject(event.value, 5000) != WAIT_OBJECT_0) throw std::runtime_error("D3D12/Vulkan handoff timeout");
|
||||
vk(vkWaitForFences(v.device, 1, &v.fence, VK_TRUE, 5000000000ull), "Vulkan completion wait"); v.submitted = false;
|
||||
if (rrLayout) {
|
||||
void* data = nullptr; D3D12_RANGE range{0, SIZE_T(bytes)};
|
||||
dx(readback->Map(0, &range, &data), "RR D3D12 output map");
|
||||
try { rrLayout->validate(data, iteration); } catch (...) { D3D12_RANGE empty{}; readback->Unmap(0, &empty); throw; }
|
||||
D3D12_RANGE empty{}; readback->Unmap(0, &empty);
|
||||
continue;
|
||||
}
|
||||
uint32_t* first = nullptr; vk(vkMapMemory(v.device, v.host, 0, bytes, 0, reinterpret_cast<void**>(&first)), "Vulkan readback map");
|
||||
auto padding = [&](uint32_t word) { return imageMode && word % (rowPitch / 4) >= imageWidth * 2; };
|
||||
size_t mismatches = 0;
|
||||
for (uint32_t word = 0; word < bytes / 4; ++word)
|
||||
mismatches += first[word] != (padding(word) ? 0xdeadbeefu : pattern(iteration, word));
|
||||
vkUnmapMemory(v.device, v.host);
|
||||
uint32_t* second = nullptr; D3D12_RANGE readRange{0, SIZE_T(bytes)};
|
||||
dx(readback->Map(0, &readRange, reinterpret_cast<void**>(&second)), "D3D12 readback map");
|
||||
for (uint32_t word = 0; word < bytes / 4; ++word) {
|
||||
const uint32_t imageWord = word % 2 ? 0x3c003a00u : (iteration % 2 ? 0x38003800u : 0x38003400u);
|
||||
const uint32_t expected = imageMode && !padding(word) ? imageWord : output;
|
||||
mismatches += second[word] != expected;
|
||||
}
|
||||
D3D12_RANGE noWrite{}; readback->Unmap(0, &noWrite);
|
||||
if (mismatches) throw std::runtime_error("Shared-buffer contents differ: " + std::to_string(mismatches));
|
||||
std::printf("RR_INTEROP_ITERATION index=%u bytes=%llu exact=1 producer_value=%llu vulkan_value=%llu consumer_value=%llu\n",
|
||||
iteration, static_cast<unsigned long long>(bytes), static_cast<unsigned long long>(ready), static_cast<unsigned long long>(done), static_cast<unsigned long long>(complete));
|
||||
}
|
||||
if (rrLayout) std::printf("PASS RR_BRIDGED_INFERENCE frames=%u guides=7 d3d12_textures=1 gpu_fence_handoff=1 standalone_reference=%u\n", iterations, !rrReference.empty());
|
||||
else std::printf("PASS RR_INTEROP_TRANSFER iterations=%u d3d12_to_vulkan_exact=1 vulkan_to_d3d12_exact=1 gpu_fence_handoff=1 canonical_image=%u inference_executed=0\n", iterations, imageMode);
|
||||
return 0;
|
||||
} catch (const std::exception& error) { std::fprintf(stderr, "FAIL RR_INTEROP_TRANSFER %s\n", error.what()); return 1; }
|
||||
@@ -0,0 +1,291 @@
|
||||
#include <volk.h>
|
||||
#include "rr_native_backend.h"
|
||||
#include "rr_native_packet.h"
|
||||
#include "rr_bridge_replay.h"
|
||||
#include "d3d12_command_hooks.h"
|
||||
#include <mutex>
|
||||
|
||||
namespace d4r::win::rr {
|
||||
namespace {
|
||||
// The engine uses Volk's global dispatch. Serialize device creation, recording,
|
||||
// submission and destruction; reload the owning dispatch before each operation.
|
||||
std::mutex dispatchMutex;
|
||||
using Packet = rr_bridge::NativePacket;
|
||||
using Device = rr_bridge::NativeDevice;
|
||||
size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; }
|
||||
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) {
|
||||
// Match Linux's direct sampled-image route. These formats preserve the
|
||||
// stored texel values; no CPU conversion or reinterpretation is involved.
|
||||
if (plane == Snapshot::Motion) {
|
||||
if (format == DXGI_FORMAT_R16G16_FLOAT) return {nullptr, VK_FORMAT_R16G16_SFLOAT, 4};
|
||||
if (format == DXGI_FORMAT_R32G32_FLOAT) return {nullptr, VK_FORMAT_R32G32_SFLOAT, 8};
|
||||
} else if (plane == Snapshot::Depth || plane == Snapshot::HitDistance || plane == Snapshot::Disocclusion || plane == Snapshot::Subsurface) {
|
||||
if (plane == Snapshot::Depth && (format == DXGI_FORMAT_R32_TYPELESS || format == DXGI_FORMAT_D32_FLOAT ||
|
||||
format == DXGI_FORMAT_R32G8X24_TYPELESS || format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT || format == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS))
|
||||
return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
|
||||
if (format == DXGI_FORMAT_R32_FLOAT) return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
|
||||
if (format == DXGI_FORMAT_R16_FLOAT) return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
|
||||
if (plane != Snapshot::Depth && format == DXGI_FORMAT_R8_UNORM) return {nullptr, VK_FORMAT_R8_UNORM, 1};
|
||||
} else {
|
||||
if (format == DXGI_FORMAT_R16G16B16A16_FLOAT) return {nullptr, VK_FORMAT_R16G16B16A16_SFLOAT, 8};
|
||||
if (format == DXGI_FORMAT_R32G32B32A32_FLOAT) return {nullptr, VK_FORMAT_R32G32B32A32_SFLOAT, 16};
|
||||
if (format == DXGI_FORMAT_R8G8B8A8_UNORM) return {nullptr, VK_FORMAT_R8G8B8A8_UNORM, 4};
|
||||
if (format == DXGI_FORMAT_R10G10B10A2_UNORM) return {nullptr, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4};
|
||||
if (plane == Snapshot::Roughness) {
|
||||
if (format == DXGI_FORMAT_R16_FLOAT) return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
|
||||
if (format == DXGI_FORMAT_R32_FLOAT) return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
|
||||
if (format == DXGI_FORMAT_R8_UNORM) return {nullptr, VK_FORMAT_R8_UNORM, 1};
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Unsupported native RR sampled input format");
|
||||
}
|
||||
DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) {
|
||||
const auto desc = resource_desc(texture);
|
||||
if (plane != Snapshot::Depth) return desc.Format;
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; UINT rows = 0; UINT64 rowBytes = 0, bytes = 0;
|
||||
device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, &rows, &rowBytes, &bytes);
|
||||
const auto format = input_format(desc.Format, plane);
|
||||
if (!bytes || bytes == UINT64_MAX || rows != desc.Height || rowBytes != desc.Width * format.bytes ||
|
||||
footprint.Footprint.Width != desc.Width || footprint.Footprint.Height != desc.Height)
|
||||
throw std::runtime_error("Unsupported RR depth-plane footprint");
|
||||
return footprint.Footprint.Format;
|
||||
}
|
||||
}
|
||||
struct Backend::Impl {
|
||||
Creation creation;
|
||||
rr_bridge::Layout layout;
|
||||
ComPtr<ID3D12Device> d3d;
|
||||
ComPtr<ID3D12CommandQueue> queue;
|
||||
ComPtr<ID3D12Resource> shared;
|
||||
ComPtr<ID3D12Fence> fence;
|
||||
ComPtr<ID3D12CommandAllocator> producerAllocator, consumerAllocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> producer, consumer;
|
||||
Handle event;
|
||||
std::unique_ptr<Device> native;
|
||||
std::unique_ptr<Packet> packet;
|
||||
std::unique_ptr<rr_bridge::Replay> replay;
|
||||
VkCommandPool pool = VK_NULL_HANDLE;
|
||||
VkCommandBuffer command = VK_NULL_HANDLE;
|
||||
VkFence completion = VK_NULL_HANDLE;
|
||||
uint64_t sequence = 0;
|
||||
bool pending = false, failed = false;
|
||||
std::unique_ptr<Snapshot> retained;
|
||||
Impl(ID3D12Device* device, const Creation& c)
|
||||
: creation(c), layout(c.width, c.height, c.outWidth, c.outHeight), d3d(device) {
|
||||
// Reserve each guide's largest supported texel size once. Format
|
||||
// changes can then preserve the packet, model and temporal history.
|
||||
VkDeviceSize offset = 0;
|
||||
for (auto& plane : layout.planes) {
|
||||
plane.offset = (offset + 511) & ~VkDeviceSize(511);
|
||||
plane.pitch = (VkDeviceSize(layout.rw) * 16 + 255) & ~VkDeviceSize(255);
|
||||
offset = plane.offset + plane.pitch * layout.rh;
|
||||
}
|
||||
layout.outputOffset = (offset + 511) & ~VkDeviceSize(511);
|
||||
layout.bytes = layout.outputOffset + layout.outputPitch * layout.oh;
|
||||
}
|
||||
void dispatch() {
|
||||
if (native && native->instance) volkLoadInstance(native->instance);
|
||||
if (native && native->device) volkLoadDevice(native->device);
|
||||
}
|
||||
~Impl() {
|
||||
dispatch();
|
||||
replay.reset();
|
||||
if (native && native->device) {
|
||||
if (completion) vkDestroyFence(native->device, completion, nullptr);
|
||||
if (pool) vkDestroyCommandPool(native->device, pool, nullptr);
|
||||
}
|
||||
packet.reset(); native.reset();
|
||||
}
|
||||
void initialize(const std::string& directory) {
|
||||
if (!d3d) throw std::runtime_error("Null native RR D3D12 device");
|
||||
// Match the Linux RR adapter's contract: HDR and render-resolution,
|
||||
// unjittered motion. D/E already perform their own exposure reduction.
|
||||
// NVIDIA/DLSS include/nvsdk_ngx_defs.h defines these public flag bits.
|
||||
if ((creation.flags & 3u) != 3u)
|
||||
throw std::runtime_error("Native RR requires HDR and render-resolution motion vectors");
|
||||
if (creation.flags & ~(3u | 8u | 64u))
|
||||
throw std::runtime_error("Native RR creation flags require an unimplemented conversion");
|
||||
if (creation.quality < 0 || creation.quality > 5) throw std::runtime_error("Unsupported RR quality mode");
|
||||
native = std::make_unique<Device>(); native->initialize(adapter_luid(d3d.Get()), true);
|
||||
replay = std::make_unique<rr_bridge::Replay>(layout, native->physical, native->device, directory);
|
||||
const unsigned indices[] = {3, 2, 1, 4, 5, 0};
|
||||
const auto requested = creation.presets[indices[creation.quality]];
|
||||
if (requested && requested != replay->model->preset()) throw std::runtime_error("Configured RR model differs from requested preset");
|
||||
shared = make_buffer(d3d.Get(), layout.bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
|
||||
dx(d3d->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&fence)), "RR shared fence");
|
||||
Handle memoryHandle, fenceHandle;
|
||||
dx(d3d->CreateSharedHandle(shared.Get(), nullptr, GENERIC_ALL, nullptr, &memoryHandle.value), "RR packet handle");
|
||||
dx(d3d->CreateSharedHandle(fence.Get(), nullptr, GENERIC_ALL, nullptr, &fenceHandle.value), "RR fence handle");
|
||||
packet = std::make_unique<Packet>(*native); packet->initialize(layout.bytes, memoryHandle.value, fenceHandle.value);
|
||||
event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!event.value) throw std::runtime_error("RR completion event creation failed");
|
||||
auto createList = [&](auto& allocator, auto& list) {
|
||||
dx(d3d->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "RR allocator");
|
||||
dx(d3d->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "RR command list");
|
||||
dx(list->Close(), "RR initial close");
|
||||
};
|
||||
createList(producerAllocator, producer); createList(consumerAllocator, consumer);
|
||||
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; pci.queueFamilyIndex = native->family;
|
||||
Device::check(vkCreateCommandPool(native->device, &pci, nullptr, &pool), "RR command pool");
|
||||
VkCommandBufferAllocateInfo cai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||||
cai.commandPool = pool; cai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cai.commandBufferCount = 1;
|
||||
Device::check(vkAllocateCommandBuffers(native->device, &cai, &command), "RR command buffer");
|
||||
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
Device::check(vkCreateFence(native->device, &fci, nullptr, &completion), "RR Vulkan completion fence");
|
||||
}
|
||||
void validate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
|
||||
if (failed) throw std::runtime_error("Native RR backend is poisoned after failed submission");
|
||||
if (!q) throw std::runtime_error("Null RR submission queue");
|
||||
D3D12_COMMAND_QUEUE_DESC queueDesc{};
|
||||
#if defined(__MINGW32__)
|
||||
q->GetDesc(&queueDesc);
|
||||
#else
|
||||
queueDesc = q->GetDesc();
|
||||
#endif
|
||||
if (queueDesc.Type != D3D12_COMMAND_LIST_TYPE_DIRECT) throw std::runtime_error("RR requires a direct queue");
|
||||
ComPtr<ID3D12Device> owner; dx(q->GetDevice(IID_PPV_ARGS(&owner)), "RR queue device");
|
||||
if (owner.Get() != d3d.Get()) throw std::runtime_error("RR queue belongs to another device");
|
||||
for (size_t i = 0; i < access.size(); ++i)
|
||||
if (frame.resources[i] && (access[i].pending_split || access[i].inherited)) throw std::runtime_error("Unresolved RR texture state");
|
||||
validate_frame(frame);
|
||||
}
|
||||
void validate_frame(const Snapshot& frame) {
|
||||
if (failed) throw std::runtime_error("Native RR backend is poisoned after failed submission");
|
||||
if (frame.renderWidth != layout.rw || frame.renderHeight != layout.rh)
|
||||
throw std::runtime_error("Dynamic RR render extent requires engine reconfiguration");
|
||||
if (frame.resources[Snapshot::Exposure]) throw std::runtime_error("RR exposure texture is not implemented");
|
||||
if (frame.exposureScale != 1 || frame.depthType || frame.normalsType || frame.roughnessType || frame.diffuseType || frame.specularType)
|
||||
throw std::runtime_error("RR guide encoding or exposure scale requires conversion");
|
||||
for (size_t i = 0; i < Snapshot::Count; ++i) {
|
||||
if (i == Snapshot::Exposure) continue;
|
||||
auto* resource = frame.resources[i].Get();
|
||||
if (!resource && i >= Snapshot::HitDistance) continue;
|
||||
if (!resource) throw std::runtime_error("Missing required native RR texture");
|
||||
ComPtr<ID3D12Device> resourceOwner; dx(resource->GetDevice(IID_PPV_ARGS(&resourceOwner)), "RR texture device");
|
||||
if (resourceOwner.Get() != d3d.Get()) throw std::runtime_error("RR texture belongs to another device");
|
||||
const auto desc = resource_desc(resource);
|
||||
if (i != Snapshot::Output) (void)input_format(desc.Format, i);
|
||||
if (i == Snapshot::Depth) {
|
||||
(void)copy_format(d3d.Get(), resource, i);
|
||||
// D3D12 requires whole-subresource copies for depth-stencil.
|
||||
// The copied subresource is depth plane 0, never stencil.
|
||||
if ((desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) &&
|
||||
(frame.origins[i][0] || frame.origins[i][1] || layout.rw != desc.Width || layout.rh != desc.Height))
|
||||
throw std::runtime_error("RR depth-stencil requires a full depth-plane rectangle");
|
||||
}
|
||||
const uint32_t width = i == Snapshot::Output ? layout.ow : layout.rw;
|
||||
const uint32_t height = i == Snapshot::Output ? layout.oh : layout.rh;
|
||||
if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D || (i == Snapshot::Output && desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT) || desc.SampleDesc.Count != 1 || desc.DepthOrArraySize != 1 ||
|
||||
frame.origins[i][0] > desc.Width || width > desc.Width - frame.origins[i][0] ||
|
||||
frame.origins[i][1] > desc.Height || height > desc.Height - frame.origins[i][1])
|
||||
throw std::runtime_error("Unsupported native RR texture format or subrect");
|
||||
if (i == Snapshot::Output && !(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
|
||||
throw std::runtime_error("RR output lacks UAV capability");
|
||||
}
|
||||
for (size_t i = 0; i < Snapshot::Count; ++i)
|
||||
if (i != Snapshot::Output && frame.resources[i] == frame.resources[Snapshot::Output]) throw std::runtime_error("RR input/output alias is unsupported");
|
||||
}
|
||||
void evaluate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
|
||||
validate(q, frame, access); dispatch();
|
||||
for (size_t i = 0; i < layout.planes.size(); ++i) {
|
||||
const auto resource = resource_index(i);
|
||||
layout.planes[i].enabled = frame.resources[resource] != nullptr;
|
||||
if (!layout.planes[i].enabled) continue;
|
||||
const auto format = input_format(resource_desc(frame.resources[resource].Get()).Format, resource);
|
||||
replay->configure(i, format.format);
|
||||
layout.planes[i].format = format.format; layout.planes[i].bytes = format.bytes;
|
||||
}
|
||||
// Recording mutates engine upload/history bookkeeping. Any later error
|
||||
// requires recreation, even if no GPU submission has started yet.
|
||||
failed = true;
|
||||
commands::InternalScope internal;
|
||||
dx(producerAllocator->Reset(), "RR producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "RR producer reset");
|
||||
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
for (size_t i = 0; i < layout.planes.size(); ++i) {
|
||||
const auto resource = resource_index(i);
|
||||
auto* texture = frame.resources[resource].Get(); const auto& plane = layout.planes[i];
|
||||
if (!plane.enabled) continue;
|
||||
texture_copy_barrier(producer.Get(), texture, access[resource], false, false);
|
||||
auto source = rr_bridge::D3D12Frames::texture(texture);
|
||||
auto dest = rr_bridge::D3D12Frames::placed(shared.Get(), plane.offset, plane.pitch,
|
||||
copy_format(d3d.Get(), texture, resource), layout.rw, layout.rh);
|
||||
const auto x = frame.origins[resource][0], y = frame.origins[resource][1];
|
||||
D3D12_BOX box{x, y, 0, x + layout.rw, y + layout.rh, 1};
|
||||
const bool depthStencil = (resource_desc(texture).Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) != 0;
|
||||
producer->CopyTextureRegion(&dest, 0, 0, 0, &source, depthStencil ? nullptr : &box);
|
||||
texture_copy_barrier(producer.Get(), texture, access[resource], false, true);
|
||||
}
|
||||
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
|
||||
dx(producer->Close(), "RR producer close");
|
||||
dx(consumerAllocator->Reset(), "RR consumer allocator reset"); dx(consumer->Reset(consumerAllocator.Get(), nullptr), "RR consumer reset");
|
||||
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
auto* output = frame.resources[Snapshot::Output].Get();
|
||||
texture_copy_barrier(consumer.Get(), output, access[Snapshot::Output], true, false);
|
||||
auto source = rr_bridge::D3D12Frames::placed(shared.Get(), layout.outputOffset, layout.outputPitch,
|
||||
DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
|
||||
auto dest = rr_bridge::D3D12Frames::texture(output);
|
||||
consumer->CopyTextureRegion(&dest, frame.origins[Snapshot::Output][0], frame.origins[Snapshot::Output][1], 0, &source, nullptr);
|
||||
texture_copy_barrier(consumer.Get(), output, access[Snapshot::Output], true, true);
|
||||
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON);
|
||||
dx(consumer->Close(), "RR consumer close");
|
||||
Device::check(vkResetCommandPool(native->device, pool, 0), "RR Vulkan pool reset");
|
||||
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||
Device::check(vkBeginCommandBuffer(command, &begin), "RR Vulkan begin");
|
||||
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = packet->buffer; acquire.size = VK_WHOLE_SIZE;
|
||||
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = native->family;
|
||||
acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
|
||||
replay->record(command, packet->buffer, frame.settings);
|
||||
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = native->family; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
|
||||
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
|
||||
vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
|
||||
Device::check(vkEndCommandBuffer(command), "RR Vulkan end");
|
||||
const uint64_t ready = sequence + 1, done = sequence + 2, complete = sequence + 3;
|
||||
VkTimelineSemaphoreSubmitInfo values{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO};
|
||||
values.waitSemaphoreValueCount = values.signalSemaphoreValueCount = 1; values.pWaitSemaphoreValues = &ready; values.pSignalSemaphoreValues = &done;
|
||||
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO, &values}; submit.waitSemaphoreCount = submit.signalSemaphoreCount = 1;
|
||||
submit.pWaitSemaphores = submit.pSignalSemaphores = &packet->semaphore; submit.pWaitDstStageMask = &stage;
|
||||
submit.commandBufferCount = 1; submit.pCommandBuffers = &command;
|
||||
Device::check(vkResetFences(native->device, 1, &completion), "RR completion reset");
|
||||
retained = std::make_unique<Snapshot>(frame); queue = q; pending = true;
|
||||
try {
|
||||
ID3D12CommandList* inputs[] = {producer.Get()}; q->ExecuteCommandLists(1, inputs);
|
||||
dx(q->Signal(fence.Get(), ready), "RR input ready signal");
|
||||
Device::check(vkQueueSubmit(native->queue, 1, &submit, completion), "RR model submission");
|
||||
dx(q->Wait(fence.Get(), done), "RR output queue wait");
|
||||
ID3D12CommandList* outputs[] = {consumer.Get()}; q->ExecuteCommandLists(1, outputs);
|
||||
dx(q->Signal(fence.Get(), complete), "RR completion signal");
|
||||
dx(fence->SetEventOnCompletion(complete, event.value), "RR completion event");
|
||||
if (WaitForSingleObject(event.value, 5000) != WAIT_OBJECT_0) throw std::runtime_error("RR D3D12 completion timeout");
|
||||
Device::check(vkWaitForFences(native->device, 1, &completion, VK_TRUE, 5000000000ull), "RR Vulkan completion wait");
|
||||
if (fence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("RR D3D12 device removed");
|
||||
pending = false; failed = false; retained.reset(); queue.Reset(); sequence = complete;
|
||||
} catch (...) { failed = true; throw; }
|
||||
}
|
||||
};
|
||||
Backend::Backend(ID3D12Device* device, const Creation& creation, const std::string& directory) {
|
||||
std::lock_guard<std::mutex> lock(dispatchMutex);
|
||||
auto candidate = std::make_unique<Impl>(device, creation);
|
||||
candidate->initialize(directory); impl = std::move(candidate);
|
||||
}
|
||||
Backend::~Backend() {
|
||||
std::lock_guard<std::mutex> lock(dispatchMutex);
|
||||
if (impl && impl->pending) {
|
||||
std::fprintf(stderr, "D4R_RR_QUARANTINE incomplete GPU work; retaining backend and guide resources\n");
|
||||
(void)impl.release();
|
||||
} else impl.reset();
|
||||
}
|
||||
void Backend::evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
|
||||
std::lock_guard<std::mutex> lock(dispatchMutex);
|
||||
impl->evaluate(queue, frame, access);
|
||||
}
|
||||
void Backend::validate_frame(const Snapshot& frame) {
|
||||
std::lock_guard<std::mutex> lock(dispatchMutex);
|
||||
impl->validate_frame(frame);
|
||||
}
|
||||
void Backend::drain() {
|
||||
std::lock_guard<std::mutex> lock(dispatchMutex);
|
||||
if (impl->pending || impl->failed) throw std::runtime_error("RR backend cannot retire failed GPU work");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include "rr_ngx_snapshot.h"
|
||||
#include <memory>
|
||||
|
||||
namespace d4r::win::rr {
|
||||
// Native RR execution on borrowed game resources. No fixture files, synthetic
|
||||
// guide creation, child process, NVIDIA runtime, or CPU tensor transfer.
|
||||
class Backend {
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
public:
|
||||
Backend(ID3D12Device* device, const Creation& creation, const std::string& modelDirectory);
|
||||
~Backend();
|
||||
Backend(const Backend&) = delete;
|
||||
Backend& operator=(const Backend&) = delete;
|
||||
void validate_frame(const Snapshot& frame);
|
||||
void evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access);
|
||||
void drain();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <volk.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rr_bridge {
|
||||
// Owns only Vulkan objects. The caller supplies the actual D3D12 adapter LUID;
|
||||
// production callers must obtain it from the borrowed game's device.
|
||||
struct NativeDevice {
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physical = VK_NULL_HANDLE;
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkQueue queue = VK_NULL_HANDLE;
|
||||
uint32_t family = 0;
|
||||
NativeDevice() = default;
|
||||
NativeDevice(const NativeDevice&) = delete;
|
||||
NativeDevice& operator=(const NativeDevice&) = delete;
|
||||
static void check(VkResult result, const char* operation) {
|
||||
if (result != VK_SUCCESS)
|
||||
throw std::runtime_error(std::string(operation) + " VkResult=" + std::to_string(result));
|
||||
}
|
||||
void initialize(const LUID& luid, bool inference) {
|
||||
if (instance) throw std::runtime_error("RR Vulkan device already initialized");
|
||||
check(volkInitialize(), "Vulkan loader");
|
||||
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = VK_API_VERSION_1_3;
|
||||
VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; ici.pApplicationInfo = &app;
|
||||
check(vkCreateInstance(&ici, nullptr, &instance), "Vulkan instance"); volkLoadInstance(instance);
|
||||
uint32_t count = 0;
|
||||
check(vkEnumeratePhysicalDevices(instance, &count, nullptr), "Vulkan device count");
|
||||
std::vector<VkPhysicalDevice> devices(count);
|
||||
check(vkEnumeratePhysicalDevices(instance, &count, devices.data()), "Vulkan devices");
|
||||
for (auto candidate : devices) {
|
||||
VkPhysicalDeviceIDProperties id{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id};
|
||||
vkGetPhysicalDeviceProperties2(candidate, &properties);
|
||||
if (id.deviceLUIDValid && !std::memcmp(id.deviceLUID, &luid, VK_LUID_SIZE)) physical = candidate;
|
||||
}
|
||||
if (!physical) throw std::runtime_error("D3D12/Vulkan adapter LUID mismatch");
|
||||
uint32_t countFamilies = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physical, &countFamilies, nullptr);
|
||||
std::vector<VkQueueFamilyProperties> properties(countFamilies);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physical, &countFamilies, properties.data());
|
||||
family = countFamilies;
|
||||
for (uint32_t i = 0; i < countFamilies; ++i)
|
||||
if (properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { family = i; break; }
|
||||
if (family == countFamilies) throw std::runtime_error("No Vulkan compute queue");
|
||||
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cm{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR};
|
||||
VkPhysicalDeviceVulkan13Features v13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, &cm};
|
||||
VkPhysicalDeviceVulkan12Features v12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, &v13};
|
||||
VkPhysicalDeviceVulkan11Features v11{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &v12};
|
||||
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &v11};
|
||||
vkGetPhysicalDeviceFeatures2(physical, &features);
|
||||
if (!v12.timelineSemaphore) throw std::runtime_error("Timeline semaphore unsupported");
|
||||
if (inference && (!cm.cooperativeMatrix || !v12.shaderFloat16 || !v11.storageBuffer16BitAccess || !v13.subgroupSizeControl))
|
||||
throw std::runtime_error("Missing RR cooperative-matrix/fp16/subgroup feature");
|
||||
if (!inference) v13.pNext = nullptr;
|
||||
float priority = 1;
|
||||
VkDeviceQueueCreateInfo qi{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
||||
qi.queueFamilyIndex = family; qi.queueCount = 1; qi.pQueuePriorities = &priority;
|
||||
std::vector<const char*> extensions{"VK_KHR_external_memory_win32", "VK_KHR_external_semaphore_win32"};
|
||||
if (inference) extensions.push_back(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME);
|
||||
VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &features};
|
||||
di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi;
|
||||
di.enabledExtensionCount = uint32_t(extensions.size()); di.ppEnabledExtensionNames = extensions.data();
|
||||
check(vkCreateDevice(physical, &di, nullptr, &device), "Vulkan device"); volkLoadDevice(device);
|
||||
vkGetDeviceQueue(device, family, 0, &queue);
|
||||
}
|
||||
// Callers must retire submitted work before destroying this owner. A failed
|
||||
// submission may instead quarantine the owner and its dependent resources.
|
||||
~NativeDevice() {
|
||||
if (device) vkDestroyDevice(device, nullptr);
|
||||
if (instance) vkDestroyInstance(instance, nullptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "rr_native_device.h"
|
||||
#include <vulkan/vulkan_win32.h>
|
||||
|
||||
namespace rr_bridge {
|
||||
// Imports the caller's D3D12 shared packet and fence. Exported Win32 handles
|
||||
// remain caller-owned; Vulkan keeps its own references after successful import.
|
||||
struct NativePacket {
|
||||
NativeDevice& native;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkSemaphore semaphore = VK_NULL_HANDLE;
|
||||
explicit NativePacket(NativeDevice& owner) : native(owner) {}
|
||||
NativePacket(const NativePacket&) = delete;
|
||||
NativePacket& operator=(const NativePacket&) = delete;
|
||||
void initialize(VkDeviceSize bytes, HANDLE resource, HANDLE fence) {
|
||||
if (buffer || !bytes || !resource || !fence)
|
||||
throw std::runtime_error("Invalid RR shared packet import");
|
||||
auto memoryProperties = reinterpret_cast<PFN_vkGetMemoryWin32HandlePropertiesKHR>(
|
||||
vkGetDeviceProcAddr(native.device, "vkGetMemoryWin32HandlePropertiesKHR"));
|
||||
auto importSemaphore = reinterpret_cast<PFN_vkImportSemaphoreWin32HandleKHR>(
|
||||
vkGetDeviceProcAddr(native.device, "vkImportSemaphoreWin32HandleKHR"));
|
||||
if (!memoryProperties || !importSemaphore) throw std::runtime_error("Missing Win32 import entry points");
|
||||
VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
|
||||
external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT;
|
||||
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; bi.size = bytes;
|
||||
bi.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
NativeDevice::check(vkCreateBuffer(native.device, &bi, nullptr, &buffer), "External Vulkan buffer");
|
||||
VkMemoryRequirements req; vkGetBufferMemoryRequirements(native.device, buffer, &req);
|
||||
VkMemoryWin32HandlePropertiesKHR handleProperties{VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR};
|
||||
NativeDevice::check(memoryProperties(native.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT,
|
||||
resource, &handleProperties), "D3D12 resource memory types");
|
||||
VkPhysicalDeviceMemoryProperties properties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(native.physical, &properties);
|
||||
const uint32_t bits = req.memoryTypeBits & handleProperties.memoryTypeBits;
|
||||
uint32_t type = properties.memoryTypeCount;
|
||||
for (uint32_t i = 0; i < properties.memoryTypeCount; ++i)
|
||||
if ((bits & (1u << i)) && (properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
|
||||
type = i; break;
|
||||
}
|
||||
if (type == properties.memoryTypeCount) throw std::runtime_error("No compatible RR packet memory type");
|
||||
VkImportMemoryWin32HandleInfoKHR imported{VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR};
|
||||
imported.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT; imported.handle = resource;
|
||||
VkMemoryDedicatedAllocateInfo dedicated{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, &imported};
|
||||
dedicated.buffer = buffer;
|
||||
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &dedicated};
|
||||
ai.allocationSize = req.size; ai.memoryTypeIndex = type;
|
||||
NativeDevice::check(vkAllocateMemory(native.device, &ai, nullptr, &memory), "Dedicated D3D12 memory import");
|
||||
NativeDevice::check(vkBindBufferMemory(native.device, buffer, memory, 0), "Bind imported buffer");
|
||||
VkSemaphoreTypeCreateInfo timeline{VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO};
|
||||
timeline.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
|
||||
VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &timeline};
|
||||
NativeDevice::check(vkCreateSemaphore(native.device, &sci, nullptr, &semaphore), "Vulkan timeline semaphore");
|
||||
VkImportSemaphoreWin32HandleInfoKHR si{VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR};
|
||||
si.semaphore = semaphore; si.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT; si.handle = fence;
|
||||
NativeDevice::check(importSemaphore(native.device, &si), "D3D12 fence import");
|
||||
}
|
||||
// The owner must retire GPU submissions before destroying an import.
|
||||
~NativePacket() {
|
||||
if (semaphore) vkDestroySemaphore(native.device, semaphore, nullptr);
|
||||
if (buffer) vkDestroyBuffer(native.device, buffer, nullptr);
|
||||
if (memory) vkFreeMemory(native.device, memory, nullptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
// Synthetic NGX feature-13 lifecycle and delayed-submission test. No game access.
|
||||
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
|
||||
#include <volk.h>
|
||||
#include "d3d12_external.h"
|
||||
#include "rr_bridge_replay.h"
|
||||
#include "ngx_parameters.h"
|
||||
#include "ngx_public_abi.h"
|
||||
#include <dxgi1_4.h>
|
||||
|
||||
using namespace d4r::win;
|
||||
int main(int argc, char** argv) try {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
if (argc != 6 && argc != 7) throw std::runtime_error("Usage: rr_ngx_probe SHIM INPUTS REFERENCE OUTPUT MODEL [contracts]");
|
||||
const std::string mode = argc == 7 ? argv[6] : "";
|
||||
const bool contracts = mode.find("contracts") != std::string::npos;
|
||||
const bool inherited = mode.find("inherited") != std::string::npos;
|
||||
const bool enhanced = mode.find("enhanced") != std::string::npos;
|
||||
const bool split = mode.find("split") != std::string::npos;
|
||||
const bool unavailable = mode.find("unavailable") != std::string::npos;
|
||||
const bool withoutSr = mode.find("without-sr") != std::string::npos;
|
||||
const bool floatGuides = mode.find("float-guides") != std::string::npos;
|
||||
const bool switchGuides = mode.find("switch-guides") != std::string::npos;
|
||||
const bool autoExposure = mode.find("auto-exposure") != std::string::npos;
|
||||
DXGI_FORMAT depthFormat = DXGI_FORMAT_UNKNOWN;
|
||||
if (mode.find("depth-d32s8-typeless") != std::string::npos) depthFormat = DXGI_FORMAT_R32G8X24_TYPELESS;
|
||||
else if (mode.find("depth-d32s8") != std::string::npos) depthFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
|
||||
else if (mode.find("depth-d32") != std::string::npos) depthFormat = DXGI_FORMAT_D32_FLOAT;
|
||||
else if (mode.find("depth-r32-typeless") != std::string::npos) depthFormat = DXGI_FORMAT_R32_TYPELESS;
|
||||
const bool stencil = depthFormat == DXGI_FORMAT_R32G8X24_TYPELESS || depthFormat == DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
|
||||
if (enhanced && split) throw std::runtime_error("The split fixture currently tests legacy split barriers only");
|
||||
rr_bridge::Layout layout(argv[2], argv[4], argv[3]);
|
||||
const auto canonical = layout;
|
||||
if (floatGuides) {
|
||||
for (size_t i : {size_t(0), size_t(3)}) {
|
||||
layout.planes[i].format = VK_FORMAT_R32G32B32A32_SFLOAT; layout.planes[i].bytes = 16;
|
||||
}
|
||||
layout.planes[1].format = VK_FORMAT_R32G32_SFLOAT; layout.planes[1].bytes = 8;
|
||||
layout.planes[4].format = VK_FORMAT_R16_SFLOAT; layout.planes[4].bytes = 2;
|
||||
layout.initializePacket();
|
||||
}
|
||||
const auto floatLayout = layout;
|
||||
auto uploadFrame = [&](void* destination, unsigned index) {
|
||||
if (!floatGuides) { layout.upload(destination, index); return; }
|
||||
// CPU conversion belongs only to fixture initialization. The production
|
||||
// backend transfers and samples the actual D3D12 formats on the GPU.
|
||||
std::memset(destination, 0, size_t(layout.bytes));
|
||||
for (size_t i = 0; i < layout.planes.size(); ++i) {
|
||||
const auto& plane = layout.planes[i];
|
||||
if (!plane.enabled || (i >= 7 && !layout.frames[index].optional[i - 7])) continue;
|
||||
auto data = rr_bridge::read(layout.inputs / (std::to_string(layout.frames[index].id) + "-" + plane.name + ".bin"));
|
||||
if (data.size() != size_t(layout.rw) * layout.rh * canonical.planes[i].bytes)
|
||||
throw std::runtime_error("Invalid canonical input for float-guide fixture");
|
||||
for (uint32_t y = 0; y < layout.rh; ++y) for (uint32_t x = 0; x < layout.rw; ++x) {
|
||||
const auto* source = data.data() + (size_t(y) * layout.rw + x) * canonical.planes[i].bytes;
|
||||
auto* dest = static_cast<char*>(destination) + plane.offset + y * plane.pitch + x * plane.bytes;
|
||||
if ((i == 0 || i == 1 || i == 3) && plane.bytes != canonical.planes[i].bytes) {
|
||||
for (uint32_t c = 0; c < plane.bytes / 4; ++c) {
|
||||
uint16_t half; std::memcpy(&half, source + c * 2, 2);
|
||||
const float value = float(d4r::diag::binary16::half_value(half)); std::memcpy(dest + c * 4, &value, 4);
|
||||
}
|
||||
} else std::memcpy(dest, source, plane.bytes);
|
||||
}
|
||||
}
|
||||
};
|
||||
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "NGX RR factory");
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
for (UINT i = 0; ; ++i) {
|
||||
if (factory->EnumAdapters1(i, &adapter) == DXGI_ERROR_NOT_FOUND) throw std::runtime_error("No AMD adapter");
|
||||
DXGI_ADAPTER_DESC1 desc{}; dx(adapter->GetDesc1(&desc), "NGX RR adapter");
|
||||
if (desc.VendorId == 0x1002 && !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) break;
|
||||
adapter.Reset();
|
||||
}
|
||||
ComPtr<ID3D12Device> device; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device)), "NGX RR device");
|
||||
if (enhanced) {
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS12 support{};
|
||||
dx(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS12, &support, sizeof(support)), "RR enhanced barrier support");
|
||||
if (!support.EnhancedBarriersSupported) throw std::runtime_error("RR enhanced barriers unavailable");
|
||||
}
|
||||
auto enhancedAccess = [](bool output) {
|
||||
ResourceAccess access; access.enhanced = true;
|
||||
access.layout = output ? D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS : D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_SHADER_RESOURCE;
|
||||
access.access = output ? D3D12_BARRIER_ACCESS_UNORDERED_ACCESS : D3D12_BARRIER_ACCESS_SHADER_RESOURCE;
|
||||
return access;
|
||||
};
|
||||
auto switchBarrierModel = [&](ID3D12GraphicsCommandList* list, ID3D12Resource* texture, bool output, bool toEnhanced) {
|
||||
// Microsoft requires COMMON at each legacy/enhanced API boundary.
|
||||
// https://microsoft.github.io/DirectX-Specs/d3d/D3D12EnhancedBarriers.html
|
||||
const auto legacy = output ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
||||
if (toEnhanced) transition(list, texture, legacy, D3D12_RESOURCE_STATE_COMMON);
|
||||
ComPtr<ID3D12GraphicsCommandList7> newer; dx(list->QueryInterface(IID_PPV_ARGS(&newer)), "RR enhanced fixture list");
|
||||
const auto access = enhancedAccess(output);
|
||||
D3D12_TEXTURE_BARRIER barrier{}; barrier.pResource = texture;
|
||||
barrier.LayoutBefore = toEnhanced ? D3D12_BARRIER_LAYOUT_COMMON : access.layout;
|
||||
barrier.LayoutAfter = toEnhanced ? access.layout : D3D12_BARRIER_LAYOUT_COMMON;
|
||||
barrier.AccessBefore = toEnhanced ? D3D12_BARRIER_ACCESS_COMMON : access.access;
|
||||
barrier.AccessAfter = toEnhanced ? access.access : D3D12_BARRIER_ACCESS_COMMON;
|
||||
barrier.SyncBefore = barrier.SyncAfter = D3D12_BARRIER_SYNC_ALL;
|
||||
D3D12_BARRIER_GROUP group{}; group.Type = D3D12_BARRIER_TYPE_TEXTURE; group.NumBarriers = 1; group.pTextureBarriers = &barrier;
|
||||
newer->Barrier(1, &group);
|
||||
if (!toEnhanced) transition(list, texture, D3D12_RESOURCE_STATE_COMMON, legacy);
|
||||
};
|
||||
ComPtr<ID3D12CommandQueue> queue; D3D12_COMMAND_QUEUE_DESC q{}; q.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
dx(device->CreateCommandQueue(&q, IID_PPV_ARGS(&queue)), "NGX RR queue");
|
||||
d4r::diag::Library shim(d4r::diag::wide(std::filesystem::absolute(argv[1]).string()));
|
||||
auto init = shim.symbol<unsigned(*)(const char*, int, const char*, const wchar_t*, ID3D12Device*, unsigned, const void*)>("NVSDK_NGX_D3D12_Init_ProjectID");
|
||||
auto allocate = shim.symbol<unsigned(*)(void**)>("NVSDK_NGX_D3D12_AllocateParameters");
|
||||
auto destroy = shim.symbol<unsigned(*)(void*)>("NVSDK_NGX_D3D12_DestroyParameters");
|
||||
auto create = shim.symbol<unsigned(*)(ID3D12GraphicsCommandList*, unsigned, void*, void**)>("NVSDK_NGX_D3D12_CreateFeature");
|
||||
auto evaluate = shim.symbol<unsigned(*)(ID3D12GraphicsCommandList*, const void*, void*, void*)>("NVSDK_NGX_D3D12_EvaluateFeature");
|
||||
auto release = shim.symbol<unsigned(*)(void*)>("NVSDK_NGX_D3D12_ReleaseFeature");
|
||||
auto shutdown = shim.symbol<unsigned(*)()>("NVSDK_NGX_D3D12_Shutdown");
|
||||
auto check = [](unsigned r, const char* operation) { if (r != 1) throw std::runtime_error(std::string(operation) + " NGX=" + std::to_string(r)); };
|
||||
auto require = [](bool condition, const char* operation) { if (!condition) throw std::runtime_error(operation); };
|
||||
if (contracts || unavailable) {
|
||||
auto requirements = shim.symbol<unsigned(*)(IDXGIAdapter*, const d4r::ngx::FeatureDiscoveryInfo*, d4r::ngx::FeatureRequirement*)>("NVSDK_NGX_D3D12_GetFeatureRequirements");
|
||||
d4r::ngx::FeatureDiscoveryInfo discovery{}; discovery.FeatureID = 13;
|
||||
d4r::ngx::FeatureRequirement result{};
|
||||
check(requirements(adapter.Get(), &discovery, &result), "RR requirements");
|
||||
require((result.FeatureSupported == 0) != unavailable, "RR requirement readiness disagrees with configured backend");
|
||||
ComPtr<IDXGIAdapter> warp; dx(factory->EnumWarpAdapter(IID_PPV_ARGS(&warp)), "RR WARP adapter");
|
||||
check(requirements(warp.Get(), &discovery, &result), "RR WARP requirements");
|
||||
require(result.FeatureSupported != 0, "RR advertised on unsupported WARP adapter");
|
||||
discovery.FeatureID = 14;
|
||||
check(requirements(adapter.Get(), &discovery, &result), "Unknown feature requirements");
|
||||
require(result.FeatureSupported != 0, "Unknown NGX feature advertised");
|
||||
}
|
||||
std::string projectId = "24480451-f00d-face-1304-0308dabad187", projectVersion = "1.0";
|
||||
std::wstring featurePath = std::filesystem::absolute(argv[2]).wstring();
|
||||
const wchar_t* featurePaths[] = {featurePath.c_str()};
|
||||
d4r::ngx::FeatureCommonInfo common{}; common.PathListInfo = {featurePaths, 1};
|
||||
check(init(projectId.c_str(), 0, projectVersion.c_str(), L"", device.Get(), 0x15, &common), "RR Init");
|
||||
// Native RR defers SR startup. Its Init snapshot must survive caller-owned
|
||||
// project/path strings and path-list descriptors changing before SR use.
|
||||
projectId.assign("mutated-project"); projectVersion.assign("mutated-version");
|
||||
featurePath.assign(L"mutated-feature-path"); featurePaths[0] = nullptr;
|
||||
common.PathListInfo = {}; common.LoggingInfo.DisableOtherLoggingSinks = true;
|
||||
if (contracts || unavailable) {
|
||||
auto capabilities = shim.symbol<unsigned(*)(void**)>("NVSDK_NGX_D3D12_GetCapabilityParameters");
|
||||
void* caps = nullptr; check(capabilities(&caps), "RR capabilities");
|
||||
require(d4r::ngx::int_value(caps, "SuperSamplingDenoising.Available") == (unavailable ? 0 : 1) &&
|
||||
(d4r::ngx::int_value(caps, "SuperSamplingDenoising.FeatureInitResult") == 1) != unavailable, "RR capability readiness failed");
|
||||
require(d4r::ngx::int_value(caps, "SuperSampling.Available") == (withoutSr ? 0 : 1), "SR capability disagrees with dependency readiness");
|
||||
check(destroy(caps), "RR destroy capabilities");
|
||||
}
|
||||
void* p = nullptr; check(allocate(&p), "RR Allocate");
|
||||
d4r_ngx_set_uint(p, "Width", layout.rw); d4r_ngx_set_uint(p, "Height", layout.rh);
|
||||
d4r_ngx_set_uint(p, "OutWidth", layout.ow); d4r_ngx_set_uint(p, "OutHeight", layout.oh);
|
||||
d4r_ngx_set_int(p, "PerfQualityValue", 2);
|
||||
const unsigned creationFlags = 3u | (layout.frames.front().settings.depthInverted ? 8u : 0u) | (autoExposure ? 64u : 0u);
|
||||
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", creationFlags);
|
||||
std::printf("RR_NGX_CREATION flags=%u\n", creationFlags);
|
||||
if (unavailable) {
|
||||
void* rejected = reinterpret_cast<void*>(uintptr_t(1));
|
||||
require(create(nullptr, 13, p, &rejected) != 1 && !rejected, "Unavailable RR backend accepted creation");
|
||||
check(destroy(p), "Unavailable RR destroy parameters");
|
||||
check(shutdown(), "Unavailable RR shutdown");
|
||||
std::puts("PASS RR_NGX_UNAVAILABLE capabilities=0 requirements=unsupported creation=rejected sr_available=1");
|
||||
return 0;
|
||||
}
|
||||
if (contracts) {
|
||||
void* rejected = reinterpret_cast<void*>(uintptr_t(1));
|
||||
require(create(nullptr, 14, p, &rejected) != 1 && !rejected, "Unknown feature creation accepted");
|
||||
d4r_ngx_set_uint(p, "Width", 0); rejected = reinterpret_cast<void*>(uintptr_t(1));
|
||||
require(create(nullptr, 13, p, &rejected) != 1 && !rejected, "Invalid RR creation accepted");
|
||||
d4r_ngx_set_uint(p, "Width", layout.rw);
|
||||
for (unsigned flags : {0u, 1u, 2u, 7u, 131u}) {
|
||||
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", flags); rejected = reinterpret_cast<void*>(uintptr_t(1));
|
||||
require(create(nullptr, 13, p, &rejected) != 1 && !rejected, "Unsupported RR creation flags accepted");
|
||||
}
|
||||
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", creationFlags);
|
||||
}
|
||||
void* feature = nullptr; check(create(nullptr, 13, p, &feature), "RR CreateFeature");
|
||||
auto textures = std::make_unique<rr_bridge::D3D12Frames>(layout, device.Get(), depthFormat);
|
||||
ComPtr<ID3D12Resource> stencilUpload, stencilReadback;
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT stencilFootprint{}; UINT64 stencilBytes = 0;
|
||||
if (stencil) {
|
||||
const auto desc = resource_desc(textures->inputs[2].Get()); UINT rows = 0; UINT64 rowBytes = 0;
|
||||
device->GetCopyableFootprints(&desc, 1, 1, 0, &stencilFootprint, &rows, &rowBytes, &stencilBytes);
|
||||
require(rows == layout.rh && rowBytes == layout.rw && stencilBytes && stencilBytes != UINT64_MAX, "Unexpected stencil-plane footprint");
|
||||
stencilUpload = make_buffer(device.Get(), stencilBytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
stencilReadback = make_buffer(device.Get(), stencilBytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
}
|
||||
auto stencilTransition = [](ID3D12GraphicsCommandList* list, ID3D12Resource* image, D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) {
|
||||
D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Transition = {image, 1, from, to}; list->ResourceBarrier(1, &barrier);
|
||||
};
|
||||
auto stencilCopy = [&](ID3D12GraphicsCommandList* list, ID3D12Resource* image, bool upload) {
|
||||
D3D12_TEXTURE_COPY_LOCATION buffer{}; buffer.pResource = upload ? stencilUpload.Get() : stencilReadback.Get();
|
||||
buffer.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; buffer.PlacedFootprint = stencilFootprint;
|
||||
auto texture = rr_bridge::D3D12Frames::texture(image); texture.SubresourceIndex = 1;
|
||||
const auto copy = upload ? D3D12_RESOURCE_STATE_COPY_DEST : D3D12_RESOURCE_STATE_COPY_SOURCE;
|
||||
stencilTransition(list, image, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, copy);
|
||||
list->CopyTextureRegion(upload ? &texture : &buffer, 0, 0, 0, upload ? &buffer : &texture, nullptr);
|
||||
stencilTransition(list, image, copy, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
};
|
||||
auto upload = make_buffer(device.Get(), layout.bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
auto scratch = make_buffer(device.Get(), layout.bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
auto readback = make_buffer(device.Get(), layout.bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)), "NGX RR fixture fence");
|
||||
Handle event; event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!event.value) throw std::runtime_error("NGX RR fixture event");
|
||||
for (unsigned index = 0; index < layout.frames.size(); ++index) {
|
||||
if (switchGuides) {
|
||||
layout = index % 2 ? floatLayout : canonical;
|
||||
textures = std::make_unique<rr_bridge::D3D12Frames>(layout, device.Get(), depthFormat);
|
||||
}
|
||||
void* mapped = nullptr; D3D12_RANGE noRead{};
|
||||
dx(upload->Map(0, &noRead, &mapped), "NGX RR upload map"); uploadFrame(mapped, index); upload->Unmap(0, nullptr);
|
||||
ComPtr<ID3D12CommandAllocator> allocator; ComPtr<ID3D12GraphicsCommandList> list;
|
||||
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "NGX RR fixture allocator");
|
||||
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "NGX RR fixture list");
|
||||
if (stencil) {
|
||||
dx(stencilUpload->Map(0, &noRead, &mapped), "RR stencil upload map");
|
||||
std::memset(mapped, 0, size_t(stencilBytes));
|
||||
for (uint32_t y = 0; y < layout.rh; ++y) for (uint32_t x = 0; x < layout.rw; ++x)
|
||||
static_cast<uint8_t*>(mapped)[stencilFootprint.Offset + y * stencilFootprint.Footprint.RowPitch + x] = uint8_t(x * 13 + y * 17 + index * 29);
|
||||
stencilUpload->Unmap(0, nullptr); stencilCopy(list.Get(), textures->inputs[2].Get(), true);
|
||||
}
|
||||
uint32_t optionalMask = 0;
|
||||
for (unsigned i = 0; i < 4; ++i) if (layout.frames[index].optional[i]) optionalMask |= 1u << i;
|
||||
textures->recordInputs(list.Get(), upload.Get(), scratch.Get(), optionalMask);
|
||||
if (enhanced) {
|
||||
for (size_t i = 0; i < textures->inputs.size(); ++i)
|
||||
if (textures->inputs[i] && (i < 7 || (optionalMask & (1u << (i - 7)))))
|
||||
switchBarrierModel(list.Get(), textures->inputs[i].Get(), false, true);
|
||||
switchBarrierModel(list.Get(), textures->output.Get(), true, true);
|
||||
}
|
||||
ComPtr<ID3D12CommandAllocator> prefixAllocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> prefix;
|
||||
if (inherited) {
|
||||
// The preceding list publishes every resource state, including an
|
||||
// output which has not otherwise been used by a fixture upload.
|
||||
if (!enhanced) {
|
||||
transition(list.Get(), textures->output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COMMON);
|
||||
transition(list.Get(), textures->output.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
}
|
||||
dx(list->Close(), "NGX RR prefix close"); prefix = list; prefixAllocator = allocator;
|
||||
list.Reset(); allocator.Reset();
|
||||
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "NGX RR inherited allocator");
|
||||
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "NGX RR inherited list");
|
||||
}
|
||||
const char* names[] = {"Color", "MotionVectors", "Depth", "GBuffer.Normals", "GBuffer.Roughness", "DLSS.Input.DiffuseAlbedo", "DLSS.Input.SpecularAlbedo"};
|
||||
for (size_t i = 0; i < 7; ++i) d4r_ngx_set_d3d12_resource(p, names[i], textures->inputs[i].Get());
|
||||
d4r_ngx_set_d3d12_resource(p, "Output", textures->output.Get());
|
||||
const char* optionalNames[] = {"DLSSD.SpecularHitDistance", "DLSS.DisocclusionMask", "DLSSD.ScreenSpaceSubsurfaceScatteringGuide", "DLSSD.ColorBeforeParticles"};
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
d4r_ngx_set_d3d12_resource(p, optionalNames[i], layout.frames[index].optional[i] ? textures->inputs[i + 7].Get() : nullptr);
|
||||
auto settings = layout.frames[index].settings;
|
||||
d4r_ngx_set_float(p, "Jitter.Offset.X", settings.jitter[0]); d4r_ngx_set_float(p, "Jitter.Offset.Y", settings.jitter[1]);
|
||||
d4r_ngx_set_float(p, "MV.Scale.X", settings.motionScale[0]); d4r_ngx_set_float(p, "MV.Scale.Y", settings.motionScale[1]);
|
||||
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", settings.preExposure);
|
||||
d4r_ngx_set_uint(p, "Reset", settings.reset); d4r_ngx_set_uint(p, "DLSS.Use.HW.Depth", settings.hardwareDepth);
|
||||
d4r_ngx_set_uint(p, "DLSS.Roughness.Mode", settings.roughnessPacked);
|
||||
d4r_ngx_set_void(p, "WorldToViewMatrix", settings.worldToView); d4r_ngx_set_void(p, "ViewToClipMatrix", settings.viewToClip);
|
||||
const char* states[] = {"D4R.Color.State", "D4R.Motion.State", "D4R.Depth.State", "D4R.Normals.State", "D4R.Roughness.State", "D4R.Diffuse.State", "D4R.Specular.State"};
|
||||
if (!inherited) {
|
||||
for (const auto* state : states) d4r_ngx_set_uint(p, state, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
d4r_ngx_set_uint(p, "D4R.Output.State", D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
}
|
||||
if (contracts && index == 0) {
|
||||
auto rejects = [&](const char* name) { require(evaluate(list.Get(), feature, p, nullptr) != 1, name); };
|
||||
if (depthFormat == DXGI_FORMAT_D32_FLOAT || stencil) {
|
||||
d4r_ngx_set_uint(p, "DLSS.Input.Depth.Subrect.Base.X", 1); rejects("Cropped depth-stencil accepted");
|
||||
d4r_ngx_set_uint(p, "DLSS.Input.Depth.Subrect.Base.X", 0);
|
||||
std::puts("RR_NGX_DEPTH_SUBRECT cropped_rejected=1");
|
||||
}
|
||||
d4r_ngx_set_void(p, "ViewToClipMatrix", nullptr); rejects("Missing RR matrix accepted");
|
||||
d4r_ngx_set_void(p, "ViewToClipMatrix", settings.viewToClip);
|
||||
d4r_ngx_set_d3d12_resource(p, "DLSSD.SpecularHitDistance", textures->inputs[0].Get()); rejects("Invalid RR scalar guide format accepted");
|
||||
d4r_ngx_set_d3d12_resource(p, "DLSSD.SpecularHitDistance", layout.frames[index].optional[0] ? textures->inputs[7].Get() : nullptr);
|
||||
d4r_ngx_set_uint(p, "DLSS.Render.Subrect.Dimensions.Width", layout.rw + 1); rejects("Unsupported RR dynamic extent accepted");
|
||||
d4r_ngx_set_uint(p, "DLSS.Render.Subrect.Dimensions.Width", layout.rw);
|
||||
d4r_ngx_set_uint(p, "DLSS.Depth.Type", 1); rejects("Unsupported RR encoding accepted");
|
||||
d4r_ngx_set_uint(p, "DLSS.Depth.Type", 0);
|
||||
d4r_ngx_set_uint(p, "DLSS.Input.Color.Subrect.Base.X", layout.rw); rejects("Invalid RR subrect accepted");
|
||||
d4r_ngx_set_uint(p, "DLSS.Input.Color.Subrect.Base.X", 0);
|
||||
d4r_ngx_set_d3d12_resource(p, "Output", textures->inputs[0].Get()); rejects("Aliased or invalid RR output accepted");
|
||||
d4r_ngx_set_d3d12_resource(p, "Output", textures->output.Get());
|
||||
std::puts("RR_NGX_NEGATIVE rejected=13 command_list_preserved=1");
|
||||
}
|
||||
if (split && index == 0) {
|
||||
D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Transition = {textures->inputs[0].Get(), 0, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_SOURCE};
|
||||
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY; list->ResourceBarrier(1, &barrier);
|
||||
require(evaluate(list.Get(), feature, p, nullptr) != 1, "NGX RR accepted unfinished split barrier");
|
||||
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_END_ONLY; list->ResourceBarrier(1, &barrier);
|
||||
transition(list.Get(), textures->inputs[0].Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
|
||||
std::puts("RR_NGX_SPLIT unfinished_rejected=1 paired_before_submit=1");
|
||||
}
|
||||
check(evaluate(list.Get(), feature, p, nullptr), "RR EvaluateFeature");
|
||||
// Both pointer payloads and parameter scalars change before submission.
|
||||
settings.worldToView[0] = 999; settings.viewToClip[5] = 999;
|
||||
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 999);
|
||||
d4r_ngx_set_d3d12_resource(p, "Color", nullptr);
|
||||
auto output = textures->output;
|
||||
if (enhanced) texture_copy_barrier(list.Get(), output.Get(), enhancedAccess(true), false, false);
|
||||
else transition(list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
auto source = rr_bridge::D3D12Frames::texture(output.Get());
|
||||
auto dest = rr_bridge::D3D12Frames::placed(readback.Get(), layout.outputOffset, layout.outputPitch,
|
||||
DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
|
||||
list->CopyTextureRegion(&dest, 0, 0, 0, &source, nullptr);
|
||||
if (enhanced) {
|
||||
texture_copy_barrier(list.Get(), output.Get(), enhancedAccess(true), false, true);
|
||||
for (size_t i = 0; i < textures->inputs.size(); ++i)
|
||||
if (textures->inputs[i] && (i < 7 || (optionalMask & (1u << (i - 7)))))
|
||||
switchBarrierModel(list.Get(), textures->inputs[i].Get(), false, false);
|
||||
switchBarrierModel(list.Get(), output.Get(), true, false);
|
||||
} else transition(list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
if (stencil) stencilCopy(list.Get(), textures->inputs[2].Get(), false);
|
||||
if (index + 1 == layout.frames.size()) {
|
||||
check(release(feature), "RR Release before submission"); feature = nullptr;
|
||||
check(destroy(p), "RR Destroy parameters before submission"); p = nullptr;
|
||||
textures.reset(); output.Reset();
|
||||
}
|
||||
dx(list->Close(), "NGX RR fixture close");
|
||||
ID3D12CommandList* lists[] = {prefix ? prefix.Get() : list.Get(), list.Get()};
|
||||
queue->ExecuteCommandLists(inherited ? 2 : 1, lists);
|
||||
dx(queue->Signal(fence.Get(), index + 1), "NGX RR fixture signal");
|
||||
dx(fence->SetEventOnCompletion(index + 1, event.value), "NGX RR fixture completion");
|
||||
if (WaitForSingleObject(event.value, 10000) != WAIT_OBJECT_0) throw std::runtime_error("NGX RR fixture timeout");
|
||||
D3D12_RANGE range{0, SIZE_T(layout.bytes)}; dx(readback->Map(0, &range, &mapped), "NGX RR readback map");
|
||||
try { layout.validate(mapped, index); } catch (...) { readback->Unmap(0, nullptr); throw; }
|
||||
readback->Unmap(0, nullptr);
|
||||
if (stencil) {
|
||||
D3D12_RANGE stencilRange{0, SIZE_T(stencilBytes)};
|
||||
dx(stencilReadback->Map(0, &stencilRange, &mapped), "RR stencil verification map");
|
||||
bool exact = true;
|
||||
for (uint32_t y = 0; y < layout.rh; ++y) for (uint32_t x = 0; x < layout.rw; ++x)
|
||||
exact &= static_cast<const uint8_t*>(mapped)[stencilFootprint.Offset + y * stencilFootprint.Footprint.RowPitch + x] == uint8_t(x * 13 + y * 17 + index * 29);
|
||||
stencilReadback->Unmap(0, nullptr); require(exact, "RR bridge changed stencil values");
|
||||
}
|
||||
std::printf("RR_NGX_FRAME index=%u snapshot_mutated=1 exact=1 optional_mask=%u hardware_depth=%u depth_inverted=%u\n",
|
||||
index, optionalMask, settings.hardwareDepth, settings.depthInverted);
|
||||
}
|
||||
check(shutdown(), "RR Shutdown");
|
||||
if (contracts) std::puts("PASS RR_NGX_CONTRACTS capabilities=1 requirements=1 warp_rejected=1 invalid_inputs_rejected=1");
|
||||
if (inherited) std::puts("PASS RR_NGX_INHERITED prefix_list=1 caller_states=0");
|
||||
if (enhanced) std::puts("PASS RR_NGX_ENHANCED tracked_access=1 restored=1");
|
||||
if (split) std::puts("PASS RR_NGX_SPLIT unfinished_rejected=1");
|
||||
if (withoutSr) std::puts("PASS RR_NGX_WITHOUT_SR rr_executed=1 sr_available=0");
|
||||
if (floatGuides) std::puts("PASS RR_NGX_FLOAT_GUIDES color_rgba32=1 motion_rg32=1 normals_rgba32=1 roughness_r16=1");
|
||||
if (switchGuides) std::puts("PASS RR_NGX_SWITCH_GUIDES alternate_each_frame=1 feature_recreated=0");
|
||||
if (autoExposure) std::puts("PASS RR_NGX_FLAGS hdr=1 render_motion=1 auto_exposure=1");
|
||||
if (depthFormat != DXGI_FORMAT_UNKNOWN) std::printf("PASS RR_NGX_DEPTH format=%u stencil_preserved=%u\n", unsigned(depthFormat), stencil);
|
||||
std::puts("PASS RR_NGX_LIFECYCLE feature=13 snapshot_mutation=1 release_before_submit=1 parameter_destroy_before_submit=1");
|
||||
return 0;
|
||||
} catch (const std::exception& error) { std::fprintf(stderr, "FAIL RR_NGX %s\n", error.what()); return 1; }
|
||||
@@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
#include "ngx_parameters.h"
|
||||
#include "../../engine/runtime.h"
|
||||
#include <d3d12.h>
|
||||
#include <wrl/client.h>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace d4r::win::rr {
|
||||
inline unsigned number(void* parameters, const char* name, unsigned fallback = 0) {
|
||||
unsigned value = 0;
|
||||
if (d4r_ngx_get_uint(parameters, name, &value) == 1) return value;
|
||||
int signedValue = 0;
|
||||
if (d4r_ngx_get_int(parameters, name, &signedValue) == 1 && signedValue >= 0) return unsigned(signedValue);
|
||||
return fallback;
|
||||
}
|
||||
struct Creation {
|
||||
unsigned width = 0, height = 0, outWidth = 0, outHeight = 0;
|
||||
unsigned flags = 0, outputSubrects = 0, hardwareDepth = 0, roughnessMode = 0;
|
||||
unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0;
|
||||
int quality = 0;
|
||||
std::array<unsigned, 6> presets{};
|
||||
explicit Creation(void* parameters) {
|
||||
if (!parameters) throw std::runtime_error("Null RR creation parameters");
|
||||
width = number(parameters, "Width"); height = number(parameters, "Height");
|
||||
outWidth = number(parameters, "OutWidth"); outHeight = number(parameters, "OutHeight");
|
||||
if (!width || !height || !outWidth || !outHeight || width > 16384 || height > 16384 || outWidth > 16384 || outHeight > 16384)
|
||||
throw std::runtime_error("Invalid RR creation geometry");
|
||||
flags = number(parameters, "DLSS.Feature.Create.Flags");
|
||||
outputSubrects = number(parameters, "DLSS.Enable.Output.Subrects");
|
||||
hardwareDepth = number(parameters, "DLSS.Use.HW.Depth");
|
||||
roughnessMode = number(parameters, "DLSS.Roughness.Mode");
|
||||
depthType = number(parameters, "DLSS.Depth.Type"); normalsType = number(parameters, "DLSS.Normals.Type");
|
||||
roughnessType = number(parameters, "DLSS.Roughness.Type"); diffuseType = number(parameters, "DLSS.Diffuse.Type");
|
||||
specularType = number(parameters, "DLSS.Specular.Type");
|
||||
quality = ngx::int_value(parameters, "PerfQualityValue");
|
||||
const char* names[] = {"RayReconstruction.Hint.Render.Preset.DLAA", "RayReconstruction.Hint.Render.Preset.Quality",
|
||||
"RayReconstruction.Hint.Render.Preset.Balanced", "RayReconstruction.Hint.Render.Preset.Performance",
|
||||
"RayReconstruction.Hint.Render.Preset.UltraPerformance", "RayReconstruction.Hint.Render.Preset.UltraQuality"};
|
||||
for (size_t i = 0; i < presets.size(); ++i) presets[i] = number(parameters, names[i]);
|
||||
}
|
||||
};
|
||||
// All data that may outlive EvaluateFeature is owned here. No matrix pointer
|
||||
// or resource pointer depends on the caller's parameter object's lifetime.
|
||||
struct Snapshot {
|
||||
enum Plane : size_t { Color, Motion, Depth, Normals, Roughness, Diffuse, Specular, Output,
|
||||
Exposure, HitDistance, Disocclusion, Subsurface, BeforeParticles, Count };
|
||||
std::array<Microsoft::WRL::ComPtr<ID3D12Resource>, Count> resources;
|
||||
std::array<std::array<unsigned, 2>, Count> origins{};
|
||||
RFrameSettings settings;
|
||||
unsigned renderWidth = 0, renderHeight = 0;
|
||||
unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0;
|
||||
float exposureScale = 1;
|
||||
static ID3D12Resource* resource(void* parameters, const char* name, const char* alias = nullptr) {
|
||||
ID3D12Resource* value = nullptr;
|
||||
(void)d4r_ngx_get_d3d12_resource(parameters, name, &value);
|
||||
if (!value && alias) (void)d4r_ngx_get_d3d12_resource(parameters, alias, &value);
|
||||
return value;
|
||||
}
|
||||
Snapshot(const Creation& creation, void* parameters) {
|
||||
if (!parameters) throw std::runtime_error("Null RR frame parameters");
|
||||
const char* names[] = {"Color", "MotionVectors", "Depth", "GBuffer.Normals", "GBuffer.Roughness",
|
||||
"DLSS.Input.DiffuseAlbedo", "DLSS.Input.SpecularAlbedo", "Output", "ExposureTexture",
|
||||
"DLSSD.SpecularHitDistance", "DLSS.DisocclusionMask", "DLSSD.ScreenSpaceSubsurfaceScatteringGuide", "DLSSD.ColorBeforeParticles"};
|
||||
for (size_t i = 0; i < Count; ++i)
|
||||
resources[i] = resource(parameters, names[i], i == Diffuse ? "GBuffer.DiffuseAlbedo" : i == Specular ? "GBuffer.SpecularAlbedo" : nullptr);
|
||||
renderWidth = number(parameters, "DLSS.Render.Subrect.Dimensions.Width", creation.width);
|
||||
renderHeight = number(parameters, "DLSS.Render.Subrect.Dimensions.Height", creation.height);
|
||||
if (!renderWidth) renderWidth = creation.width;
|
||||
if (!renderHeight) renderHeight = creation.height;
|
||||
const char* prefixes[] = {"DLSS.Input.Color", "DLSS.Input.MV", "DLSS.Input.Depth", "DLSS.Input.Normals",
|
||||
"DLSS.Input.Roughness", "DLSS.Input.DiffuseAlbedo", "DLSS.Input.SpecularAlbedo", "DLSS.Output"};
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
origins[i][0] = number(parameters, (std::string(prefixes[i]) + ".Subrect.Base.X").c_str());
|
||||
origins[i][1] = number(parameters, (std::string(prefixes[i]) + ".Subrect.Base.Y").c_str());
|
||||
}
|
||||
const char* optionalPrefixes[] = {"DLSSD.SpecularHitDistance", "DLSS.DisocclusionMask",
|
||||
"DLSSD.ScreenSpaceSubsurfaceScatteringGuide", "DLSSD.ColorBeforeParticles"};
|
||||
for (size_t i = HitDistance; i < Count; ++i) {
|
||||
origins[i][0] = number(parameters, (std::string(optionalPrefixes[i - HitDistance]) + ".Subrect.Base.X").c_str());
|
||||
origins[i][1] = number(parameters, (std::string(optionalPrefixes[i - HitDistance]) + ".Subrect.Base.Y").c_str());
|
||||
}
|
||||
settings.jitter[0] = ngx::float_value(parameters, "Jitter.Offset.X");
|
||||
settings.jitter[1] = ngx::float_value(parameters, "Jitter.Offset.Y");
|
||||
settings.motionScale[0] = ngx::float_value(parameters, "MV.Scale.X", 1);
|
||||
settings.motionScale[1] = ngx::float_value(parameters, "MV.Scale.Y", 1);
|
||||
settings.preExposure = ngx::float_value(parameters, "DLSS.Pre.Exposure", 1);
|
||||
settings.frameTimeMs = ngx::float_value(parameters, "FrameTimeDeltaInMsec", 16.666667f);
|
||||
exposureScale = ngx::float_value(parameters, "DLSS.Exposure.Scale", 1);
|
||||
settings.reset = number(parameters, "Reset") != 0;
|
||||
settings.depthInverted = (creation.flags & 8) != 0;
|
||||
settings.hardwareDepth = number(parameters, "DLSS.Use.HW.Depth", creation.hardwareDepth) != 0;
|
||||
// D/E consume a bound roughness texture even in packed mode. Only the
|
||||
// absent-texture case uses normals.w; preset F has a different contract.
|
||||
settings.roughnessPacked = number(parameters, "DLSS.Roughness.Mode", creation.roughnessMode) == 1 && !resources[Roughness];
|
||||
if (settings.roughnessPacked) { resources[Roughness] = resources[Normals]; origins[Roughness] = origins[Normals]; }
|
||||
depthType = number(parameters, "DLSS.Depth.Type", creation.depthType);
|
||||
normalsType = number(parameters, "DLSS.Normals.Type", creation.normalsType);
|
||||
roughnessType = number(parameters, "DLSS.Roughness.Type", creation.roughnessType);
|
||||
diffuseType = number(parameters, "DLSS.Diffuse.Type", creation.diffuseType);
|
||||
specularType = number(parameters, "DLSS.Specular.Type", creation.specularType);
|
||||
const char* matrixNames[] = {"WorldToViewMatrix", "ViewToClipMatrix"};
|
||||
float* matrices[] = {settings.worldToView, settings.viewToClip};
|
||||
for (unsigned i = 0; i < 2; ++i) {
|
||||
void* source = nullptr;
|
||||
if (d4r_ngx_get_void(parameters, matrixNames[i], &source) != 1 || !source)
|
||||
throw std::runtime_error(std::string("Missing RR matrix: ") + matrixNames[i]);
|
||||
std::memcpy(matrices[i], source, sizeof(float) * 16);
|
||||
for (unsigned n = 0; n < 16; ++n)
|
||||
if (!std::isfinite(matrices[i][n])) throw std::runtime_error("Nonfinite RR matrix");
|
||||
}
|
||||
for (float value : {settings.jitter[0], settings.jitter[1], settings.motionScale[0], settings.motionScale[1],
|
||||
settings.preExposure, settings.frameTimeMs, exposureScale})
|
||||
if (!std::isfinite(value)) throw std::runtime_error("Nonfinite RR frame scalar");
|
||||
if (settings.preExposure <= 0 || exposureScale <= 0 || settings.frameTimeMs < 0)
|
||||
throw std::runtime_error("Invalid RR exposure or frame time");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// CPU-only checks of the NGX RR snapshot contract. No model or GPU dispatch.
|
||||
#include <volk.h>
|
||||
#include "rr_ngx_snapshot.h"
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
struct Parameters {
|
||||
void* value = d4r_ngx_parameters_create();
|
||||
Parameters() { if (!value) throw std::runtime_error("Parameter allocation failed"); }
|
||||
~Parameters() { if (value) d4r_ngx_parameters_destroy(value); }
|
||||
};
|
||||
void require(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
template<class F> void rejects(F operation, const char* message) {
|
||||
try { operation(); } catch (const std::runtime_error&) { return; }
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
int main() try {
|
||||
using namespace d4r::win::rr;
|
||||
Parameters parameters;
|
||||
void* p = parameters.value;
|
||||
// RR callers can publish dimensions as signed NGX integers.
|
||||
d4r_ngx_set_int(p, "Width", 64); d4r_ngx_set_uint(p, "Height", 48);
|
||||
d4r_ngx_set_uint(p, "OutWidth", 128); d4r_ngx_set_uint(p, "OutHeight", 96);
|
||||
d4r_ngx_set_int(p, "DLSS.Feature.Create.Flags", 8);
|
||||
d4r_ngx_set_int(p, "DLSS.Use.HW.Depth", 1);
|
||||
d4r_ngx_set_int(p, "DLSS.Roughness.Mode", 1);
|
||||
d4r_ngx_set_uint(p, "RayReconstruction.Hint.Render.Preset.Quality", 4);
|
||||
Creation creation(p);
|
||||
require(creation.width == 64 && creation.presets[1] == 4, "RR creation values lost");
|
||||
float view[16]{}, clip[16]{};
|
||||
for (unsigned i = 0; i < 16; ++i) { view[i] = float(i + 1); clip[i] = float(i + 32); }
|
||||
d4r_ngx_set_void(p, "WorldToViewMatrix", view); d4r_ngx_set_void(p, "ViewToClipMatrix", clip);
|
||||
d4r_ngx_set_float(p, "Jitter.Offset.X", .25f);
|
||||
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 2.f);
|
||||
d4r_ngx_set_uint(p, "DLSS.Input.Normals.Subrect.Base.X", 7);
|
||||
d4r_ngx_set_uint(p, "DLSSD.SpecularHitDistance.Subrect.Base.X", 5);
|
||||
d4r_ngx_set_uint(p, "DLSSD.ColorBeforeParticles.Subrect.Base.Y", 9);
|
||||
d4r_ngx_set_uint(p, "DLSS.Use.HW.Depth", 0);
|
||||
Snapshot frame(creation, p);
|
||||
view[0] = -91; clip[15] = -93;
|
||||
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 3.f);
|
||||
d4r_ngx_set_uint(p, "Width", 777);
|
||||
d4r_ngx_set_uint(p, "RayReconstruction.Hint.Render.Preset.Quality", 5);
|
||||
require(frame.settings.worldToView[0] == 1 && frame.settings.viewToClip[15] == 47, "RR matrices were not copied");
|
||||
require(frame.settings.preExposure == 2 && frame.settings.jitter[0] == .25f, "RR scalars were not copied");
|
||||
require(creation.width == 64 && creation.presets[1] == 4, "RR creation depends on mutable parameters");
|
||||
require(frame.settings.depthInverted && !frame.settings.hardwareDepth, "RR frame override lost");
|
||||
require(frame.renderWidth == 64 && frame.renderHeight == 48, "RR render dimensions lost creation fallback");
|
||||
require(frame.settings.roughnessPacked && frame.origins[Snapshot::Roughness][0] == 7, "RR packed roughness origin lost");
|
||||
require(frame.origins[Snapshot::HitDistance][0] == 5 && frame.origins[Snapshot::BeforeParticles][1] == 9,
|
||||
"RR optional guide origins lost");
|
||||
auto copied = frame;
|
||||
require(copied.settings.worldToView[0] == 1, "Copied RR snapshot lost matrix ownership");
|
||||
{
|
||||
Parameters frameParameters;
|
||||
d4r_ngx_set_void(frameParameters.value, "WorldToViewMatrix", view);
|
||||
d4r_ngx_set_void(frameParameters.value, "ViewToClipMatrix", clip);
|
||||
Snapshot baseline(creation, frameParameters.value);
|
||||
require(baseline.settings.hardwareDepth && baseline.settings.roughnessPacked,
|
||||
"RR frame settings lost creation baseline");
|
||||
require(baseline.settings.preExposure == 1 && baseline.settings.motionScale[0] == 1,
|
||||
"RR frame scalar defaults incorrect");
|
||||
}
|
||||
view[3] = std::numeric_limits<float>::infinity();
|
||||
rejects([&] { Snapshot bad(creation, p); }, "Nonfinite RR matrix accepted");
|
||||
view[3] = 4;
|
||||
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 0);
|
||||
rejects([&] { Snapshot bad(creation, p); }, "Zero RR pre-exposure accepted");
|
||||
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 1);
|
||||
d4r_ngx_set_float(p, "Jitter.Offset.Y", std::numeric_limits<float>::quiet_NaN());
|
||||
rejects([&] { Snapshot bad(creation, p); }, "Nonfinite RR jitter accepted");
|
||||
d4r_ngx_set_float(p, "Jitter.Offset.Y", 0);
|
||||
d4r_ngx_set_void(p, "ViewToClipMatrix", nullptr);
|
||||
rejects([&] { Snapshot bad(creation, p); }, "Missing RR matrix accepted");
|
||||
d4r_ngx_set_uint(p, "Width", 0);
|
||||
rejects([&] { Creation bad(p); }, "Zero RR creation width accepted");
|
||||
// The completed snapshot must still be valid after the parameter object dies.
|
||||
d4r_ngx_parameters_destroy(parameters.value); parameters.value = nullptr;
|
||||
require(copied.settings.viewToClip[15] == 47 && copied.settings.preExposure == 2, "RR snapshot retained parameter storage");
|
||||
std::puts("PASS RR_SNAPSHOT cpu_only=1 matrices_owned=1 scalars_owned=1 creation_fallback=1 invalid_rejected=1");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "FAIL RR_SNAPSHOT %s\n", error.what()); return 1;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
"""Run the synthetic production NGX feature-13 harness in a bounded process."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--contracts', action='store_true', help='also check capability readiness and negative contracts')
|
||||
parser.add_argument('--inherited', action='store_true', help='inherit access from a preceding list in the same submission batch')
|
||||
parser.add_argument('--enhanced', action='store_true', help='exercise enhanced texture access and restoration')
|
||||
parser.add_argument('--split', action='store_true', help='reject a legacy split barrier, then complete the pair and evaluate')
|
||||
parser.add_argument('--unavailable', choices=('disabled', 'missing-model', 'fsr'), help='test unavailable RR configuration without inference')
|
||||
parser.add_argument('--without-sr', action='store_true', help='remove SR dependency configuration and require native RR execution')
|
||||
parser.add_argument('--float-guides', action='store_true', help='exercise equivalent float32 color/motion/normals and scalar roughness')
|
||||
parser.add_argument('--switch-guides', action='store_true', help='alternate canonical and float guide formats without recreating feature')
|
||||
parser.add_argument('--auto-exposure', action='store_true', help='also set the public NGX AutoExposure creation flag')
|
||||
parser.add_argument('--depth-format', choices=('r32', 'r32-typeless', 'd32', 'd32s8', 'd32s8-typeless'), default='r32', help='exercise native depth-plane copy format')
|
||||
parser.add_argument('--player-config', action='store_true', help='stage a private player folder and select RR only through d4r.ini')
|
||||
parser.add_argument('--use-existing-ini', action='store_true', help='preserve the selected runtime INI when staging player validation')
|
||||
for name in ('probe', 'shim', 'runtime', 'core', 'dlss', 'model', 'inputs', 'reference', 'output'):
|
||||
parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve())
|
||||
args = parser.parse_args()
|
||||
if args.use_existing_ini and not args.player_config:
|
||||
parser.error('--use-existing-ini requires --player-config')
|
||||
if args.player_config and (args.without_sr or args.unavailable or not args.contracts):
|
||||
parser.error('--player-config requires --contracts, configured SR dependencies and native RR')
|
||||
if args.enhanced and args.split:
|
||||
parser.error('--split currently covers legacy barriers; use a separate --enhanced run')
|
||||
if args.without_sr and (args.unavailable or not args.contracts):
|
||||
parser.error('--without-sr requires --contracts and a configured native RR backend')
|
||||
if args.switch_guides and not args.float_guides:
|
||||
parser.error('--switch-guides requires --float-guides')
|
||||
build = Path(__file__).resolve().parents[2] / 'build'
|
||||
if not args.output.is_relative_to(build.resolve()):
|
||||
parser.error('output must stay under ignored build/')
|
||||
args.output.mkdir(parents=True, exist_ok=False)
|
||||
env = {key: value for key, value in os.environ.items()
|
||||
if not key.startswith('D4R_') and key not in ('ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
|
||||
runtime = args.runtime
|
||||
native = runtime / 'native'
|
||||
if (native / 'gfx1101').is_dir():
|
||||
native = native / 'gfx1101'
|
||||
env.update(HIP_PATH=str(runtime / 'hip'), D4R_HIP_ROOT=str(runtime / 'hip'), D4R_NVCUDA_DLL=str(runtime / 'zluda/nvcuda.dll'),
|
||||
ZLUDA_CUDA_LIB=str(runtime / 'zluda/nvcuda.dll'), D4R_DLSS_DLL=str(args.dlss),
|
||||
D4R_NGX_CORE=str(args.core), D4R_NVAPI_DLL=str(runtime / 'nvapi/nvapi64.dll'),
|
||||
D4R_NVAPI_BACKEND=str(runtime / 'nvapi/backend/nvapi64.dll'),
|
||||
D4R_FORMAT_MODULE=str(runtime / 'pixel_convert_gfx1101.hsaco'),
|
||||
D4R_ZLUDA_NATIVE_DIR=str(native), D4R_QUIET_API='1',
|
||||
D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_F16_REFERENCE='1',
|
||||
D4R_NGX_DATA_DIR=str(args.output / 'ngx-data'), D4R_RR_BACKEND='native', D4R_RR_MODEL_DIR=str(args.model))
|
||||
if args.unavailable == 'disabled':
|
||||
env.pop('D4R_RR_BACKEND')
|
||||
elif args.unavailable == 'missing-model':
|
||||
missing = args.output / 'missing-model'
|
||||
env['D4R_RR_MODEL_DIR'] = str(missing)
|
||||
elif args.unavailable == 'fsr':
|
||||
env['D4R_RR_BACKEND'] = 'fsr'
|
||||
if args.without_sr:
|
||||
for key in ('HIP_PATH', 'D4R_HIP_ROOT', 'D4R_NVCUDA_DLL', 'ZLUDA_CUDA_LIB', 'D4R_DLSS_DLL',
|
||||
'D4R_NGX_CORE', 'D4R_NVAPI_DLL', 'D4R_NVAPI_BACKEND', 'D4R_FORMAT_MODULE', 'D4R_ZLUDA_NATIVE_DIR'):
|
||||
env.pop(key, None)
|
||||
else:
|
||||
env['PATH'] = str(runtime / 'hip/bin') + os.pathsep + str(runtime / 'zluda') + os.pathsep + env['PATH']
|
||||
command = [str(args.probe), str(args.shim), str(args.inputs), str(args.reference),
|
||||
str(args.output / 'frames'), str(args.model)]
|
||||
if args.player_config:
|
||||
staged = args.output / 'player'
|
||||
shutil.copytree(runtime, staged)
|
||||
shutil.copy2(args.shim, staged / 'd4r_nvngx.dll')
|
||||
for source, relative in ((args.core, 'ngx/_nvngx.dll'), (args.dlss, 'dlss/nvngx_dlss.dll')):
|
||||
target = staged / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target)
|
||||
if not args.use_existing_ini:
|
||||
config = (Path(__file__).resolve().parents[2] / 'packaging/windows/d4r.ini').read_text()
|
||||
config = config.replace('Backend = off', 'Backend = native').replace('ModelDir = auto', 'ModelDir = ' + str(args.model))
|
||||
(staged / 'd4r.ini').write_text(config)
|
||||
env = {key: value for key, value in os.environ.items()
|
||||
if not key.startswith('D4R_') and key not in ('HIP_PATH', 'ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
|
||||
command[1] = str(staged / 'd4r_nvngx.dll')
|
||||
mode = '-'.join(name for name in ('contracts', 'inherited', 'enhanced', 'split', 'unavailable') if getattr(args, name))
|
||||
if args.without_sr:
|
||||
mode += '-without-sr'
|
||||
if args.float_guides:
|
||||
mode += '-float-guides'
|
||||
if args.switch_guides:
|
||||
mode += '-switch-guides'
|
||||
if args.auto_exposure:
|
||||
mode += '-auto-exposure'
|
||||
if args.depth_format != 'r32':
|
||||
mode += '-depth-' + args.depth_format
|
||||
if mode:
|
||||
command.append(mode)
|
||||
report = {'passed': False, 'gameAccessed': False, 'qualityReference': False,
|
||||
'artifacts': {str(path): hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
for path in (args.probe, args.shim, args.core, args.dlss)}}
|
||||
try:
|
||||
with (args.output / 'stdout.log').open('wb') as out, (args.output / 'stderr.log').open('wb') as err:
|
||||
result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=180)
|
||||
report['exitCode'] = result.returncode
|
||||
text = (args.output / 'stdout.log').read_text(errors='replace')
|
||||
if args.unavailable:
|
||||
if result.returncode or 'PASS RR_NGX_UNAVAILABLE' not in text:
|
||||
raise RuntimeError('Unavailable NGX RR checks failed; inspect ' + str(args.output))
|
||||
report.update(passed=True, unavailable=args.unavailable, rrAvailable=False, srAvailable=True,
|
||||
creationRejected=True, frames=0)
|
||||
print('PASS NGX RR unavailable=' + args.unavailable + ' sr_available=1')
|
||||
return
|
||||
if result.returncode or 'PASS RR_NGX_LIFECYCLE' not in text:
|
||||
raise RuntimeError('NGX RR harness failed; inspect ' + str(args.output))
|
||||
if args.contracts and 'PASS RR_NGX_CONTRACTS' not in text:
|
||||
raise RuntimeError('NGX RR contract checks incomplete')
|
||||
if args.inherited and 'PASS RR_NGX_INHERITED' not in text:
|
||||
raise RuntimeError('NGX RR inherited access checks incomplete')
|
||||
for name in ('enhanced', 'split'):
|
||||
if getattr(args, name) and 'PASS RR_NGX_' + name.upper() not in text:
|
||||
raise RuntimeError('NGX RR ' + name + ' access checks incomplete')
|
||||
if args.without_sr and ('PASS RR_NGX_WITHOUT_SR' not in text or 'D4R_RUNTIME platform=Windows' in text):
|
||||
raise RuntimeError('NGX RR did not prove execution without initialized SR runtime')
|
||||
if args.float_guides and 'PASS RR_NGX_FLOAT_GUIDES' not in text:
|
||||
raise RuntimeError('NGX RR alternate float guide checks incomplete')
|
||||
if args.switch_guides and 'PASS RR_NGX_SWITCH_GUIDES' not in text:
|
||||
raise RuntimeError('NGX RR temporal guide-format switching checks incomplete')
|
||||
if args.auto_exposure and 'PASS RR_NGX_FLAGS' not in text:
|
||||
raise RuntimeError('NGX RR creation flag checks incomplete')
|
||||
depth_values = {'r32-typeless': 39, 'd32': 40, 'd32s8': 20, 'd32s8-typeless': 19}
|
||||
if args.depth_format != 'r32':
|
||||
marker = f'PASS RR_NGX_DEPTH format={depth_values[args.depth_format]} stencil_preserved={int(args.depth_format.startswith("d32s8"))}'
|
||||
if marker not in text:
|
||||
raise RuntimeError('NGX RR depth-format/stencil checks incomplete')
|
||||
if args.contracts and args.depth_format.startswith('d32') and 'RR_NGX_DEPTH_SUBRECT cropped_rejected=1' not in text:
|
||||
raise RuntimeError('NGX RR cropped depth-stencil rejection missing')
|
||||
expected = [line.split()[1] + '-out.bin' for line in (args.inputs / 'frames.txt').read_text().splitlines()
|
||||
if line.startswith('frame ')]
|
||||
outputs = sorted(path.name for path in (args.output / 'frames').glob('*-out.bin'))
|
||||
if outputs != sorted(expected):
|
||||
raise RuntimeError('Incomplete NGX RR output set')
|
||||
for name in expected:
|
||||
if (args.output / 'frames' / name).read_bytes() != (args.reference / name).read_bytes():
|
||||
raise RuntimeError('NGX RR reference mismatch: ' + name)
|
||||
optional_names = ('hitdistance', 'disocclusion', 'sss', 'particles')
|
||||
optional_frames = {name: sum((args.inputs / (frame.removesuffix('-out.bin') + '-' + name + '.bin')).is_file()
|
||||
for frame in expected) for name in optional_names}
|
||||
masks = [sum(1 << index for index, name in enumerate(optional_names)
|
||||
if (args.inputs / (frame.removesuffix('-out.bin') + '-' + name + '.bin')).is_file())
|
||||
for frame in expected]
|
||||
frame_lines = [line.split() for line in (args.inputs / 'frames.txt').read_text().splitlines() if line.startswith('frame ')]
|
||||
for index, mask in enumerate(masks):
|
||||
depth_mode = int(frame_lines[index][8])
|
||||
marker = (f'RR_NGX_FRAME index={index} snapshot_mutated=1 exact=1 optional_mask={mask}'
|
||||
f' hardware_depth={int(bool(depth_mode & 2))} depth_inverted={int(bool(depth_mode & 1))}')
|
||||
if marker not in text:
|
||||
raise RuntimeError('Incomplete NGX optional-guide/depth setting checks')
|
||||
report.update(passed=True, frames=len(expected), standaloneExact=True, contracts=args.contracts, inherited=args.inherited,
|
||||
enhanced=args.enhanced, split=args.split,
|
||||
withoutSr=args.without_sr,
|
||||
floatGuides=args.float_guides,
|
||||
switchGuides=args.switch_guides,
|
||||
creationFlags=int(next(line.split('flags=')[1] for line in text.splitlines()
|
||||
if line.startswith('RR_NGX_CREATION flags='))),
|
||||
optionalGuideFrames=optional_frames, optionalMasks=masks,
|
||||
depthFormat=args.depth_format, stencilPreserved=args.depth_format.startswith('d32s8'),
|
||||
hardwareDepthFrames=sum(bool(int(line[8]) & 2) for line in frame_lines),
|
||||
playerConfig=args.player_config,
|
||||
preservedPlayerIni=args.use_existing_ini,
|
||||
invertedDepthFrames=sum(bool(int(line[8]) & 1) for line in frame_lines),
|
||||
snapshotMutation=True, releaseBeforeSubmit=True, parameterDestroyBeforeSubmit=True)
|
||||
print('PASS NGX RR frames=' + str(len(expected)) + ' standalone_exact=1')
|
||||
finally:
|
||||
(args.output / 'summary.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user