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:
ayo committed 2026-10-16 12:53:26 -04:00
1 parent 3345fffcd3
commit 247fe1092d
60 files changed
+7168 -66

No files matched your search

+1
View File
@@ -15,6 +15,7 @@ __pycache__/
.venv/
.tools/
/external/MinHook/
/external/ZLUDA/
.vscode/
/windows/m11/build/
/windows/m11/.tools/
+46
View File
@@ -5,6 +5,11 @@ if(NOT WIN32 OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(d4r_fsr_rr_probe EXCLUDE_FROM_ALL tools/fsr_rr_probe.cpp)
target_link_libraries(d4r_fsr_rr_probe PRIVATE d3d12 dxgi dxguid)
if(MINGW)
target_link_options(d4r_fsr_rr_probe PRIVATE -municode -static)
endif()
set(D4R_HIP_ROOT "$ENV{HIP_PATH}" CACHE PATH "One stable HIP SDK or TheRock installation")
if(NOT D4R_HIP_ROOT AND EXISTS "C:/Program Files/AMD/ROCm/7.2")
set(D4R_HIP_ROOT "C:/Program Files/AMD/ROCm/7.2")
@@ -121,11 +126,20 @@ if(D4R_BUILD_NATIVE_ENGINE)
add_library(d4r_volk STATIC "${D4R_VOLK_ROOT}/volk.c")
target_include_directories(d4r_volk PUBLIC "${D4R_VOLK_ROOT}" "${D4R_VULKAN_HEADERS_ROOT}/include")
target_compile_definitions(d4r_volk PUBLIC VK_NO_PROTOTYPES)
add_executable(d4r_rr_interop_capabilities EXCLUDE_FROM_ALL tools/windows/rr_interop_capabilities.cpp)
target_link_libraries(d4r_rr_interop_capabilities PRIVATE d4r_volk d3d12 dxgi dxguid)
add_executable(d4r_rr_interop_probe EXCLUDE_FROM_ALL tools/windows/rr_interop_probe.cpp)
target_link_libraries(d4r_rr_interop_probe PRIVATE d4r_volk d3d12 dxgi dxguid)
if(MINGW)
target_link_options(d4r_rr_interop_capabilities PRIVATE -static)
target_link_options(d4r_rr_interop_probe PRIVATE -static)
endif()
add_library(d4r_engine STATIC engine/runtime.cpp engine/game.cpp engine/game_m.cpp engine/game_rr.cpp)
target_include_directories(d4r_engine PUBLIC "${D4R_VULKAN_HEADERS_ROOT}/include")
target_compile_definitions(d4r_engine PUBLIC VK_NO_PROTOTYPES)
target_precompile_headers(d4r_engine PRIVATE engine/volk_pch.h)
target_link_libraries(d4r_engine PUBLIC d4r_volk)
target_link_libraries(d4r_rr_interop_probe PRIVATE d4r_engine)
foreach(runner IN ITEMS k m rr)
if(runner STREQUAL "k")
set(runner_source engine/replay.cpp)
@@ -187,6 +201,11 @@ set_target_properties(d4r_nvngx_windows PROPERTIES PREFIX "" OUTPUT_NAME "d4r_nv
target_compile_definitions(d4r_nvngx_windows PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
target_include_directories(d4r_nvngx_windows PRIVATE "${D4R_HIP_ROOT}/include")
target_link_libraries(d4r_nvngx_windows PRIVATE version dbghelp d3d12 dxguid minhook bcrypt)
if(TARGET d4r_engine)
target_sources(d4r_nvngx_windows PRIVATE tools/windows/rr_native_backend.cpp)
target_link_libraries(d4r_nvngx_windows PRIVATE d4r_engine)
target_compile_definitions(d4r_nvngx_windows PRIVATE D4R_NATIVE_RR_BACKEND=1)
endif()
install(FILES "${D4R_MINHOOK_ROOT}/LICENSE.txt" DESTINATION licenses RENAME MinHook.txt)
if(MINGW)
target_link_options(d4r_nvngx_windows PRIVATE -static)
@@ -195,6 +214,13 @@ install(TARGETS d4r_nvngx_windows RUNTIME DESTINATION bin)
# Target-independent _nvngx.dll of drag-in releases: loads _nvngx_<arch>.dll
# for the game's actual HIP architecture. Its own D4R_TARGET_ARCH is unused.
add_library(d4r_nvngx_loader SHARED tools/windows/d4r_nvngx_loader.cpp)
add_executable(d4r_player_config_probe EXCLUDE_FROM_ALL tools/windows/player_config_probe.cpp)
target_include_directories(d4r_player_config_probe PRIVATE "${D4R_HIP_ROOT}/include")
target_compile_definitions(d4r_player_config_probe PRIVATE __HIP_PLATFORM_AMD__)
target_link_libraries(d4r_player_config_probe PRIVATE version dbghelp bcrypt)
if(MINGW)
target_link_options(d4r_player_config_probe PRIVATE -municode -static)
endif()
set_target_properties(d4r_nvngx_loader PROPERTIES PREFIX "" OUTPUT_NAME "d4r_nvngx_loader")
target_compile_definitions(d4r_nvngx_loader PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__)
target_include_directories(d4r_nvngx_loader PRIVATE "${D4R_HIP_ROOT}/include")
@@ -219,6 +245,26 @@ else()
target_sources(d4r_d3d12_dlss_probe PRIVATE "${ngx_parameter_object}" tools/d4r_ngx_param_host.cpp)
target_sources(d4r_nvngx_windows PRIVATE "${ngx_parameter_object}" tools/d4r_ngx_param_host.cpp)
endif()
if(TARGET d4r_engine)
add_executable(d4r_rr_ngx_probe EXCLUDE_FROM_ALL tools/windows/rr_ngx_probe.cpp tools/d4r_ngx_param_host.cpp)
target_include_directories(d4r_rr_ngx_probe PRIVATE "${D4R_HIP_ROOT}/include")
target_compile_definitions(d4r_rr_ngx_probe PRIVATE __HIP_PLATFORM_AMD__)
target_link_libraries(d4r_rr_ngx_probe PRIVATE d4r_engine d3d12 dxgi dxguid)
if(MSVC)
target_sources(d4r_rr_ngx_probe PRIVATE tools/d4r_ngx_param_msvc.cpp)
else()
target_sources(d4r_rr_ngx_probe PRIVATE "${ngx_parameter_object}")
target_link_options(d4r_rr_ngx_probe PRIVATE -static)
endif()
add_executable(d4r_rr_snapshot_probe EXCLUDE_FROM_ALL tools/windows/rr_snapshot_probe.cpp tools/d4r_ngx_param_host.cpp)
target_link_libraries(d4r_rr_snapshot_probe PRIVATE d4r_engine)
if(MSVC)
target_sources(d4r_rr_snapshot_probe PRIVATE tools/d4r_ngx_param_msvc.cpp)
else()
target_sources(d4r_rr_snapshot_probe PRIVATE "${ngx_parameter_object}")
target_link_options(d4r_rr_snapshot_probe PRIVATE -static)
endif()
endif()
add_library(d4r_nvapi_trace SHARED tools/windows/nvapi_trace.cpp)
set_target_properties(d4r_nvapi_trace PROPERTIES PREFIX "" OUTPUT_NAME "nvapi64"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-trace"
+46
View File
@@ -0,0 +1,46 @@
# Native Windows FSR Ray Regeneration investigation
The native D3D12 provider check currently fails on RX 7700 XT. This does not
implement a working Windows FSR RR backend or DLSS RR replacement.
AMD SDK 2.3's unmodified denoiser 1.2.0.2740 loads successfully and accepts the
provider query, but returns an empty provider list. The installed driver reports
shader model 6.6. No context creation or denoiser dispatch follows this result.
The DLL's Authenticode signature was valid, signed by Advanced Micro Devices;
its SHA256 matches the Linux reference's tested binary:
`48f1e5888ba6a0a3d59a98b9751e37c392b0f7b8c223d0082d5c1f40642879d3`.
Linux's RDNA3 route uses vkd3d-proton, dxil-spirv scalar FP8 emulation, and
`wmma_rdna3_workaround`. Those are not native Windows driver features. Native
parity requires a compatible shader execution route as well as the DLSS guide
conversion and internal SR adapter. Merely importing the Linux backend or
selecting `Backend=fsr` would not supply those pieces.
## Reproduce without a game
```powershell
./scripts/windows/setup-fsr-rr.ps1
cmake --build build/native-engine-windows-gfx1101 --target d4r_fsr_rr_probe
./scripts/windows/test-fsr-rr.ps1 `
-Probe build/native-engine-windows-gfx1101/d4r_fsr_rr_probe.exe `
-DenoiserDll .tools/fsr-rr-1.2/amd_fidelityfx_denoiser_dx12.dll `
-OutputDirectory build/fsr-rr-new-query -QueryOnly
```
Use a new output folder under ignored `build/`. The wrapper pins the DLL hash,
captures stdout/stderr, limits execution to 120 seconds, and propagates failure.
Without `-QueryOnly`, the harness proceeds only if a provider exists: it measures
48 synthetic frames with static/moving cameras and a reset against noise-free
signals. A successful query alone is not execution or image-quality evidence.
The Windows adapter helper queries the device's DXGI identity. It does not load
Proton driver shims or modify drivers, GPU settings, or security policy.
## Source provenance
`tools/fsr_rr_probe.cpp`, `tools/fsr_rr_scene.h`, `tools/d4r_fsr_rr.h`, and the
MIT-licensed FidelityFX API headers were imported from Linux reference
`34e6b0fe71d770729000a553f79b784e9b2dc279`. The probe includes the native Windows
identity helper instead of Linux's sysfs/vkd3d identity helper. AMD's DLL stays
under ignored `.tools/` and is not included in source or release packages.
Evidence: `build/fsr-rr-native-wrapper-query/probe.json`. No game was touched.
+180
View File
@@ -0,0 +1,180 @@
# Windows K performance work
Linux's reported 1.5 ms at 1080p and 2.47 ms at 1440p remain targets, not verified
Windows results. The synthetic measurements below include CPU synchronization,
format/array copies and NGX execution. They are not directly comparable to a
GPU network-only timer or a game's frame time.
## Reproducible interop comparison
`tools/windows/benchmark_k_interop.py` uses an existing private gfx1101 runtime
and the native D3D12 harness. It runs control/batch/batch/control, with 16 frames
per run and the first four excluded from timing. Every frame is read back;
candidate output hashes must match the control frame for frame. The harness
also rejects nonfinite or uniform outputs. No game is launched or modified.
Use the current installer core, not an older isolated NGX fixture:
```powershell
python tools/windows/benchmark_k_interop.py `
--probe build/windows-rdna4-therock-gfx1101/d4r_d3d12_dlss_probe.exe `
--shim build/windows-rdna4-therock-gfx1101/d4r_nvngx.dll `
--runtime build/got-k-test/work/package-gfx1101-current/d4r `
--core build/driver-617.14-core-check/_nvngx.dll `
--dlss build/got-k-test/isolated-sr/sr/nvngx_dlss.dll `
--output build/k-new-benchmark --resolution 2560x1440
```
Paths above identify local ignored artifacts; provision matching artifacts on
another machine. The runtime argument supplies HIP, ZLUDA, NVAPI, the gfx1101
pixel converter and native K objects. Output must be a new folder under
`build/`. Each process has a 180-second limit. Reports retain executable/DLL
hashes and native object hashes. `--profile-stages` adds CPU stage diagnostics;
its timings include logging overhead and must be reported separately.
The harness otherwise defaults to half-resolution input. This runner explicitly
sets nominal Quality input: 1280x720 for 1920x1080 output; 1706x960 for 2560x1440.
Games can round the render width differently (GoT used 1712 at 1440p).
## Evidence, RX 7700 XT, 2026-10-16
The 617.14 core SHA256 is
`66767018c36b3bab46398dade3adf173daa3730fda75965689ea848c9bc4e79b`;
SR 310.9.1 matches the installer pin. Native K encoders are reported loaded in
the captured stderr. Baseline RGBA16F inputs/output need no format conversion.
At 1706x960 to 2560x1440, pooled warmed CPU interop medians were 5.2615 ms
control and 5.0800 ms batch. All 64 frame outputs passed and matched exactly
between modes. Evidence: `build/k-interop-batch-quality-1440-core617/summary.json`.
At 1280x720 to 1920x1080, the same check passed all 64 frames; pooled warmed
medians were 3.5730 ms control and 3.3820 ms batch. Evidence:
`build/k-interop-batch-quality-1080-core617/summary.json`.
These short runs suggest only a small batching benefit; defaults remain unchanged.
This does not establish image quality on realistic ray-traced/game content.
Earlier investigation runs used core 596.36 or stage profiling. Their artifacts
are retained separately and are not the current-core baseline. Batched input
copies alone do not establish Linux speed parity. Next optimization needs paired
GPU kernel timings and an exact-output comparison, particularly for the native
gfx11 kernels' Windows lane-exchange workaround and the translated texture stages.
## Kernel experiments and GPU profile
The experiment builder now accepts an explicit gfx11/gfx12 target. gfx11 keeps
its Windows LDS lane-exchange workaround and strict FP16 accumulation. Gfx12-only
tiling/storage experiments are rejected on gfx11. Compiler arguments, target,
object hash and ISA remain in the experiment output directory.
`benchmark_k_interop.py --candidate-native DIR` compares an experimental native
folder against the existing runtime, using identical interop settings. Candidate
runs must match every output file before their timing is accepted. No candidate
is installed into the existing runtime or a game.
A strict enc1 `ds_bpermute` replacement failed exact temporal output comparison:
eight of 16 frames differed, maximum absolute difference 0.00048828125. The fresh
LDS control passed the same comparison. The DS source experiment was removed;
its local artifacts remain under `build/k-bpermute-strict-enc1-quality-1080/`.
Earlier non-strict experiments omitted `D4R_K_FP16_BASELINE` and cannot isolate
shuffle behavior. The DS builtin also folded to `v_permlanex16`; explicit assembly
was needed to test an actual DS instruction.
Enc1 WGP mode passed all 64 frame comparisons but showed no reliable speed gain:
control/candidate pooled warmed CPU interop medians 3.5455 / 3.5865 ms. No default
or package change. Evidence: `build/k-enc1-wgp-quality-1080/summary.json`.
`--profile-kernels` enables deferred GPU events with legacy-stream profiling
explicitly allowed for this synthetic diagnostic. This changes stream dependencies
and substantially raises CPU interop time; do not compare its wall times with the
uninstrumented baseline. In the first control run, the last-four-sample GPU medians
were approximately:
| Stage | GPU ms |
|---|---:|
| translated output `hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel` | 0.820 |
| native dec0 | 0.301 |
| native enc0 | 0.293 |
| native dec1 | 0.261 |
| native enc1 | 0.213 |
All 32 instrumented frames passed exact output checks across control/candidate.
Raw logs and per-run stage analysis are in
`build/k-enc1-wgp-gpu-profile-1080/gpu-stage-analysis.json`. These are instrumented
stage measurements, not an uninstrumented total. The translated output pass is
the largest measured stage and is the next optimization target. The existing
private texture recipe needs `d4r_emit.exe`, which is absent from the current
workspace's tool/runtime/build directories. No Linux speed parity is claimed.
## Offline emitter provisioning
The dependency checkout is now local under ignored `external/ZLUDA`, pinned to
`ee2f25a180099fa42f36b2346732e1f2470a03ad` with this repository's Windows patches.
LLVM is pinned to `ff4dc1f7c9e1c64d4d69e40f4ed30c2280a96dfd` plus the audited
gfx1154 backport. Rust 1.98.1 and locked Cargo dependencies are provisioned locally.
All four PTX helper variants built with TheRock's compiler. They have not yet
been linked or exercised by the emitter. LLVM library compilation is in progress.
After the LLVM build succeeds, run:
```powershell
./scripts/windows/build-zluda-windows.ps1 -EmitterOnly
./scripts/windows/build-native-texture.ps1 `
-DlssDll build/got-k-test/isolated-sr/sr/nvngx_dlss.dll `
-Kernel hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel `
-HipRoot .tools/therock-10.2.0a20260929/_rocm_sdk_core `
-ZludaRoot dist/zluda-windows-emitter -GpuArch gfx1101 `
-OutputDirectory build/private-k-output-gfx1101
```
Emitter-only mode does not build or replace `nvcuda.dll`, NVAPI, or the tracing
runtime. It writes the offline emitter and its local C++ DLL dependencies to a
separate directory. The texture recipe resolves compiler tools from either the
TheRock `lib/llvm/bin` layout or the HIP SDK `bin` layout; derived output must
stay under ignored `build/`. Each extraction/compile operation has a 600-second
limit. Output overrides remain unvalidated until full-frame comparison passes.
## K native output override validated (2026-10-16)
LLVM completed all 2,322 library build steps. The offline emitter now builds and
passes three gfx1101 compile fixtures (pattern, f16 reference MMA, f16 WMMA).
The validated CUDA/NVAPI runtime was not rebuilt or replaced.
Two build compatibility fixes were required for TheRock's LLVM 24 frontend:
- Remove stale ThinLTO summaries after stripping unused HIP/LLVM globals.
- Assemble helper IR with the pinned LLVM 22 assembler. A small compatibility
pass preserves float bit patterns, translates the dynamic denormal attribute
to LLVM 22's equivalent, and drops optimization promises the older reader
cannot express. It does not enable relaxed math. Five tests cover finite float
widening across exponent boundaries, NaN payloads, half constants, double/string
preservation and conservative attribute handling. End-to-end exact output
checks below are the runtime gate.
The private native store override for
`hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel` built for gfx1101. The PTX recipe
replaced 13 formatted stores, four b32 stores and eight round-to-zero stores.
The runtime log confirms the new native output module is loaded.
Paired control/candidate/control-order measurements (control, candidate,
candidate, control), 16 frames per run, first four excluded:
| Nominal Quality geometry | Control CPU interop ms | Candidate CPU interop ms |
|---|---:|---:|
| 1280x720 to 1920x1080 | 3.791 | 3.440 |
| 1706x960 to 2560x1440 | 5.318 | 4.744 |
All 128 frame outputs were finite/nonuniform and matched control bytes exactly.
These CPU measurements include synchronization and copies; they do not establish
Linux GPU-time parity or game speed. No game was accessed.
A separate deferred-event diagnostic passed 32 additional exact frame checks.
The output-stage GPU medians were 0.812/0.815 ms in the two controls and
0.535/0.532 ms in the two candidates. Legacy-stream events add dependencies;
these instrumented stage measurements are not uninstrumented frame totals.
Private artifacts: `build/private-k-output-gfx1101/` and combined
`build/k-output-native-gfx1101/`. Evidence:
`build/k-output-native-quality-{1080,1440}/summary.json` and
`build/k-output-native-gpu-profile-1080/output-stage-analysis.json`.
The override derives from the user's NVIDIA DLL and remains under ignored build/;
it is not included in the previously delivered local ZIP. Other K input/output
variants, game performance and automatic local override generation remain open.
+98
View File
@@ -0,0 +1,98 @@
# Local gfx1101 drop-in package
## Current local development package: v4
`build/d4r-windows-gfx1101-local-v4.zip` is 90,720,715 bytes; SHA256
`74fbb4c78dba3e616b9bbba7dc1fe4cddf0a17855968b174f8ea69968d873dfc`.
It refreshes the verified v3 package with the current native-RR shim, player INI,
README, checker and installer sources. Every manifest file hash verifies against
the final ZIP. Exact local modified-build provenance and base ZIP hash are
recorded; no clean source commit is asserted. Existing ZIPs remain unchanged.
The package contains no NVIDIA binaries, private optimized kernels, or RR
models. RR defaults off. A locally generated D/E model can be selected through
`[RayReconstruction] Backend = native` and `ModelDir` in d4r.ini. Auto selects
`d4r/engine/rr-d`; relative paths resolve beside d4r.ini. The installer does not
generate that model yet, and FSR RR remains unavailable. This is a gfx1101-only
development package with synthetic RR coverage, not verified game RR support.
The refreshed target shim passed six exact RR hardware-depth frames through the
packaged architecture loader using player INI selection without launcher path
overrides. The staged test supplied the pinned SR/core dependencies separately
and used a separate local RR model. It covered capability/negative contracts,
optional guides, enhanced/inherited state, inverted D32S8, unchanged stencil and
Release-before-submit. Evidence: `build/rr-ngx-local-v4-loader-v1/summary.json`.
ZIP verification: `build/d4r-windows-gfx1101-local-v4.validation.json`.
The installer itself was not rerun for v4 under the current script policy; no
policy bypass or system change was used. No game was touched or release published.
The refresh helper is `tools/windows/refresh_local_package.py`. It accepts only
the pinned verified v3 base and fresh destinations under ignored build/, verifies
every base/output manifest hash, and marks output as local modified provenance.
## Preserved v3 package evidence
Local development ZIP: `build/d4r-windows-gfx1101-local-v3.zip` (92,158,725 bytes).
This package targets gfx1101, including this machine's RX 7700 XT. It is not a
multi-GPU public release, and it does not implement Windows game RR or FSR RR.
Extract its contents beside the game's main executable, then run `install.bat`
and accept the dependency download prompt. Start the game normally afterward.
The ZIP contains the shim, architecture loader, OptiScaler, native K/M objects,
HIP/ZLUDA runtime, configuration, installer and licenses. NVIDIA binaries are
excluded; the installer fetches pinned official SR/RR/core DLLs locally. It does
not install the NVIDIA driver. See the ZIP's `WINDOWS_README.txt` for placement
and existing-install handling.
Validation completed without touching a game:
- Every ZIP entry listed in the manifest matches its recorded SHA256.
- No NVIDIA SR, RR or core binary is bundled; `d4r/_nvngx.dll` is d4r's loader.
- Extracted the real ZIP into an ignored mock game folder and ran its installer
with the verified local download cache. All three installed DLL hashes match
the current pins. The installed checker passed on RX 7700 XT.
- The packaged architecture loader and target shim passed 32 synthetic K Quality
frames at 1280x720 to 1920x1080. Outputs were finite, nonuniform and identical
between control/batched-input runs. This is synthetic execution, not a game or
image-quality claim.
- V3 adds `NativeDir` configuration for locally generated complete native
directories. The exact new target shim passed eight additional synthetic
player-mode frames through the architecture loader at 256x144 to 512x288.
Its log confirms selection of the generated private K output override.
Checker fixtures pass 20/20; release package fixtures pass 6/6.
The input package is explicitly marked as a local modified build. Its clean
binary source commit is not asserted; exact file hashes are authoritative. The
dependency commits are inherited from the earlier package's provenance. No
source commit or public upload was created.
Evidence: `build/windows-drop-in-v3-validation.json` and
`build/player-private-native-output/validation.json`. Earlier package validation
remains in `build/windows-drop-in-validation.json`.
ZIP SHA256:
`e93be288f4013616cc2229be0132b40395d5031d8733bfa63886ba75ce1389ca`.
## Optional private K output optimization
Generate a complete private directory in this source workspace:
```powershell
./scripts/windows/prepare-k-native-cache.ps1 `
-DlssDll build/got-k-test/isolated-sr/sr/nvngx_dlss.dll `
-NativeDirectory build/got-k-test/work/package-gfx1101-current/d4r/native `
-OutputDirectory build/my-private-k-cache
```
This uses the locally built emitter and TheRock compiler. It verifies the input
target and pinned DLL, builds the selected output variant, combines the baseline
K/M objects, regenerates the native manifest and records hashes. It never edits
a game or configuration. Select the complete `native` subfolder with `[Paths]
`NativeDir` in the new shim's `d4r.ini` after validation. Relative paths resolve
beside `d4r.ini`; `auto` uses packaged kernels. Missing or wrong-target private
objects are rejected. The default variant currently optimized is LDR, regular
depth and high-resolution motion vectors; other variants retain the normal path.
The workflow output under `build/k-native-cache-workflow/native` is byte-identical
to the previously validated 17-object candidate. The new player-mode smoke loaded
that directory. These NVIDIA-derived objects stay local and are absent from V3.
+450
View File
@@ -0,0 +1,450 @@
# Native RR D3D12/Vulkan bridge gate
## Runtime extraction in progress (2026-10-16)
The packet layout now has a geometry-only constructor with no fixture I/O.
The model recorder also accepts an owned `RFrameSettings` value independently
of the replay frame list. Existing fixture entry points delegate to the same
packet-layout and recording code. This prepares the production integration
described below.
`rr_native_device.h` now owns the shared LUID-matched Vulkan setup, compute queue
selection, and inference feature checks. `rr_native_packet.h` owns dedicated
D3D12 resource memory import and shared-fence timeline import. The existing
probe uses both owners instead of its inline setup. Imports retain Vulkan
references while the caller keeps ownership of exported Win32 handles. These
owners require retired submissions before destruction; the probe preserves its
existing quarantine behavior after an incomplete submission.
`rr_ngx_snapshot.h` adds typed RR creation/frame snapshots, independently of the
SR parameter whitelists. It copies both 16-float matrices, creation presets and
scalar baselines, per-frame overrides, render extents and required-guide subrect
origins. COM references retain required and optional guide/output resources.
D/E packed roughness uses normals.w only when a separate roughness texture is
absent, matching the Linux reference. Missing matrices and invalid/nonfinite
settings are rejected. Feature-13 evaluation uses this snapshot.
The CPU-only `d4r_rr_snapshot_probe` passes mutation, parameter-destruction,
creation-fallback, per-frame-override, and invalid-input checks with the real NGX
parameter implementation. It does not exercise GPU execution, COM resource
retirement, command-list ordering, Create/Evaluate/Release, or game integration.
This separate CPU test did not retry the policy-blocked GPU regression wrapper.
`rr_native_backend.cpp` now compiles into the Windows shim when the native engine
is available. It borrows the supplied D3D12 device/queue and actual input/output
textures, packs required guides on the GPU, records the existing model engine,
and copies output back on the GPU. It restores legacy/enhanced resource access,
supports bounded subrect copies in canonical formats, verifies device ownership
and preset selection, and retains all snapshot resources until both APIs finish.
Completion waits are bounded; a failed in-flight backend quarantines its full
ownership graph instead of freeing live GPU resources. Recording errors poison
history/upload state and require feature recreation.
The backend rejects unresolved/split states, unsupported formats/encodings,
optional guides/exposure textures, non-unit exposure scale, unsupported creation
flags, aliased input/output, and dynamic render extents. These paths still need
implementation for broad game support. Global Volk dispatch is serialized and
reloaded for each backend operation.
The shim now routes feature-13 Create/Evaluate/Release to the native backend
when `D4R_RR_BACKEND=native` and absolute `D4R_RR_MODEL_DIR` are configured.
Required-guide access is captured at Evaluate, inherited access is resolved at
submission, and the callback retains the feature and owned snapshot. Capability
and feature-requirement queries load-check the actual model, Vulkan adapter
features, and packet/fence import; they do not claim image-quality validation.
Default configurations continue to report RR unavailable. SR routing remains
separate. Native RR Init now defers the independent CUDA SR runtime, as described
in the independent-initialization evidence below.
`d4r_rr_ngx_probe` and `tools/windows/test_rr_ngx.py` add a bounded, synthetic
NGX lifecycle gate using fixture textures and same-engine reference images.
The gate mutates matrices/scalars after Evaluate and releases the last feature,
parameters, and fixture texture ownership before submission. It does not touch
a game or retry the policy-blocked interop executable/wrapper. Initial attempts
stopped before RR creation because of an invalid legacy application ID and a
missing `HIP_PATH`; those failed artifacts remain separate.
The actual native RX 7700 XT feature-13 gate now passes all six 64x64 to
128x128 camera/jitter/exposure/reset frames, byte-identical to standalone replay.
Every output is finite and nonuniform. The test includes parameter mutation
after Evaluate and feature/parameter/fixture-ownership release before the final
submission. Prefix guide uploads and suffix output readback exercise logical
command-list ordering through the real shim. Independent output-byte checks
also pass in the wrapper. Evidence: `build/rr-ngx-camera-reset-v3/summary.json`,
stdout/stderr and frame files. This is synthetic lifecycle/bridge consistency
against the same RR engine, not a game or NVIDIA quality reference. Additional
contract and inherited-state checks are recorded in the next section.
## Additional NGX contract and inherited-state validation (2026-10-16)
Unsupported frame contracts now fail synchronously during Evaluate, before a
logical command-list boundary is recorded. Submission revalidates the textures
and resolved states. This prevents unsupported guides/encodings/extents from
being reported as a successful Evaluate that fails only during submission.
The real-shim contract run verifies configured RR availability/init success,
preserved SR availability, feature-13 requirements on the actual adapter, WARP
rejection, unknown feature rejection, and eight creation/evaluation failures.
Those failures cover an unknown feature ID, zero creation width, missing matrix,
unsupported optional hit distance, dynamic render width, unsupported encoding,
out-of-bounds subrect, and an invalid/aliased output. All six valid camera/reset
frames still match standalone bytes after these rejected calls.
Evidence: `build/rr-ngx-contracts-camera-reset-v1/summary.json` and logs.
The harness can also omit all caller state overrides and upload guides in a
separate preceding list within the same ExecuteCommandLists batch. The NGX
list must resolve those inherited states at its delayed callback. That mode
passes six camera/reset frames at 64x64 to 128x128 and two full-size frames at
853x480 to 2560x1440, including the existing mutation/release-before-submit
checks. Every output remains finite/nonuniform and byte-identical to standalone.
Evidence: `build/rr-ngx-inherited-camera-reset-v1/summary.json` and
`build/rr-ngx-inherited-1440-v1/summary.json`. Enable the new gates with
`tools/windows/test_rr_ngx.py --contracts` or `--inherited`, alongside the
required explicit artifact/input/output arguments shown by `--help`.
These are synthetic native feature-13 contract/ordering results. The following
section adds enhanced access, legacy split rejection, and unavailable-backend
checks. Stale-but-loadable models, concurrency, optional guides, format conversion,
FSR RR, game integration, quality and performance parity remain open. No
additional SR presets or game content were tested.
## Enhanced access, split rejection, and unavailable configurations (2026-10-16)
`--enhanced --inherited` now validates feature-13 transfers when a preceding
list establishes enhanced shader-resource/UAV access and the NGX list has no
caller state overrides. Fixture transitions cross COMMON when switching
between legacy and enhanced barriers, as required by the
[Microsoft enhanced-barrier specification](https://microsoft.github.io/DirectX-Specs/d3d/D3D12EnhancedBarriers.html).
The backend restores enhanced access before suffix readback/fixture reuse. All
six camera/reset frames match standalone bytes and remain finite/nonuniform.
Evidence: `build/rr-ngx-enhanced-inherited-camera-reset-v1/summary.json`.
`--split` records a legacy BEGIN_ONLY transition, confirms Evaluate rejects
the unfinished split, completes the matching END_ONLY transition, and then
runs the normal feature-13 evaluation. The subsequent six camera/reset frames
still match standalone exactly. Evidence:
`build/rr-ngx-split-camera-reset-v1/summary.json`. This tests legacy split
rejection, not enhanced split rejection. No debug-layer validation is claimed.
`--unavailable disabled`, `--unavailable missing-model`, and
`--unavailable fsr` separately verify requirements stay unsupported, RR capability
availability stays zero with a failure init result, and feature-13 creation
fails with a null output handle. SR availability remains one in every case.
These runs dispatch no RR frames. Evidence is under
`build/rr-ngx-unavailable-{disabled,missing-model,fsr}-v1/summary.json`.
The FSR case proves honest rejection of the unavailable backend; it is not
FSR execution or provider support.
Native RR optional-guide/format conversion, dynamic geometry,
concurrent lifecycle, game integration, quality and performance
parity remain incomplete. The original full parity goal remains open.
## Independent native RR initialization (2026-10-16)
With an explicitly selected native RR backend, Init retains the borrowed D3D12
device and an owned initialization contract without loading HIP, CUDA, NVAPI,
NVIDIA SR/core DLLs or the SR pixel converter. Required SR capability/feature
requests initialize the separate SR runtime on demand. A missing SR dependency
reports SR unavailable without hiding a ready native RR backend. Default
configurations retain eager SR initialization. Shutdown releases both paths.
`RuntimeInitialization` owns the application data directory, project identity,
path strings, and path-list descriptor used by deferred SR startup. The harness
mutates those caller strings/descriptors after Init; subsequent SR capability
initialization still logs the original project/version and path. Both RR/SR
capabilities are available, negative contract checks pass, and all six inherited
camera/reset frames remain byte-identical to standalone.
Evidence: `build/rr-ngx-deferred-sr-init-v1/summary.json` and logs.
The `--contracts --without-sr --enhanced --inherited` gate removes all SR
dependency path configuration and does not add runtime directories to PATH.
It verifies RR requirements/capability readiness, SR unavailability, negative
contracts, and six exact native RR camera/reset frames. No SR runtime initializes.
Evidence: `build/rr-ngx-independent-camera-reset-v1/summary.json` and logs.
This proves native RR execution independent of configured SR dependencies;
it does not change the package's model provisioning or broader game support.
The default eager SR path still passes capability/readiness validation with RR
disabled: `build/rr-ngx-eager-sr-regression-v1/summary.json`. These checks add no
new SR-preset or game execution claim.
## Native sampled guide formats (2026-10-16)
The production packet now reserves each guide's maximum supported texel size,
so format changes do not recreate shared memory, the RR model, or temporal
history. The Vulkan replay caches sampled-image views by plane/format and keeps
old views alive for the engine's cached descriptors. New image variants receive
their own first-use layout transition. Production transfers still stay on GPU.
The larger reserved packet is a memory tradeoff, not a performance improvement.
The backend maps float16/float32 RGBA and UNORM RGBA8/RGB10A2 color/guide inputs,
float16/float32 two-channel motion, float16/float32 scalar depth, and scalar
float16/float32/UNORM8 roughness where applicable. Vulkan sampled/transfer support
is checked when creating a view. Unsupported encodings, typeless/depth-stencil
formats and optional guides remain rejected; output remains RGBA16F. Mapping a
format does not establish game-specific guide encoding or quality correctness.
Synthetic evidence tests RGBA32F color/normals, RG32F motion and R16F roughness,
with values exactly equivalent to the canonical fixtures. All six camera/reset
frames match standalone output bytes, including enhanced/inherited access,
negative contracts and independent SR initialization. A second run alternates
canonical and float guide formats each frame without feature recreation; all
six outputs remain exact. CPU conversions initialize fixture inputs only.
Evidence: `build/rr-ngx-float-guides-camera-reset-v1/summary.json` and
`build/rr-ngx-switch-guides-camera-reset-v1/summary.json`.
The unchanged canonical baseline also passes six frames:
`build/rr-ngx-format-layout-baseline-v1/summary.json`.
Other mapped formats have not received format-specific execution coverage.
Dynamic render extents remain rejected, as in the cited Linux reference's RR
adapter. Optional guides, output conversion, broader game formats/encodings,
concurrency, game integration, FSR RR and performance parity remain unfinished.
## Corrected RR creation-flag contract (2026-10-16)
Inspection found that the initial Windows gate accepted flagless synthetic RR
and rejected the actual HDR/render-motion flags. The production backend now
matches the Linux RR adapter: HDR and render-resolution, unjittered motion
vectors are required. Inverted depth remains supported. AutoExposure is accepted
because the D/E engine already performs its exposure reduction. Jittered motion,
high-resolution motion, non-HDR input, alpha upscaling and other unsupported
flags fail before Vulkan/model allocation. Public flag values are defined by
[NVIDIA's NGX header](https://github.com/NVIDIA/DLSS/blob/main/include/nvsdk_ngx_defs.h).
Readiness checks and the fixture now use the required HDR/MVLowRes bits. Five
additional negative creation cases cover absent flags, high-resolution motion,
non-HDR input, jittered motion and alpha upscaling, bringing the contract gate
to thirteen rejections. Both the required-flags run and the AutoExposure run
pass all six camera/reset outputs exactly against standalone. The AutoExposure
run also alternates float guide formats, uses enhanced/inherited states, and
has no configured SR dependencies. Evidence:
`build/rr-ngx-hdr-render-motion-v1/summary.json` and
`build/rr-ngx-hdr-auto-exposure-v1/summary.json`.
Earlier flagless results remain synthetic bridge/lifetime evidence; they did
not prove a valid game-facing HDR/motion contract. These new runs correct that
gap. They do not establish game quality, high-resolution/jittered motion support,
FSR execution or performance parity.
## Optional native RR guides (2026-10-16)
The production packet and recorder now carry `DLSSD.SpecularHitDistance`,
`DLSS.DisocclusionMask`, `DLSSD.ScreenSpaceSubsurfaceScatteringGuide`, and
`DLSSD.ColorBeforeParticles` into the existing RR engine. Bound optional guides
receive the same device/format/subrect/access validation and GPU texture copies
as required guides. Snapshots retain optional COM resources and subrect origins;
submission resolves their inherited/enhanced states. Binding can change between
frames without recreating the model or resetting temporal history. Exposure
textures remain unsupported. Previous optional-guide rejection notes describe
the earlier backend and are superseded by this section.
Two six-frame sequences alternate all four optional guides between present and
absent, using R32F hit distance, R16F disocclusion/subsurface and RGBA16F
pre-particle color. Every output is finite/nonuniform and matches standalone
engine replay byte-for-byte. The second sequence keeps all optional guides
bound on the final frame, when the feature, parameters and fixture texture
ownership are released before submission. Both sequences also alternate
required guide formats, use enhanced/inherited access, set valid HDR/motion/
AutoExposure flags, run thirteen negative contracts and omit SR dependencies.
Evidence: `build/rr-ngx-optional-toggle-v2/summary.json` and
`build/rr-ngx-optional-final-bound-v1/summary.json`. Reports verify actual per-frame
optional binding masks as well as independent output bytes.
The first toggle attempt (`build/rr-ngx-optional-toggle-v1/`) matched five
frames, then encountered device removal. The harness had recorded uploads and
transitions for unbound optional textures and dropped their ownership before
the final submission. Fixture commands now skip unbound guides. The backend
quarantined incomplete work as designed. No driver, TDR, security or GPU setting
was changed. Fixed toggle and final-bound lifetime gates pass.
These are synthetic same-engine API/transfer/lifetime comparisons. They do not
validate NVIDIA reference quality, additional optional format/encoding cases,
nonzero optional GPU subrects, dynamic geometry, concurrency, game integration,
FSR execution, or performance parity.
## Native depth-plane formats and stencil preservation (2026-10-16)
The backend now accepts R32 typeless, D32 float, and typed/typeless D32S8 depth
resources in addition to ordinary float depth textures. It queries the actual
D3D12 subresource-0 footprint, validates the depth row size, and imports only
those float depth bytes into Vulkan. The stencil plane is not part of the RR
packet or resource transition. Depth-stencil copies require the complete depth
rectangle with zero origin; cropped depth-stencil input fails Evaluate before
recording a boundary. The whole-rectangle rule follows
[Microsoft's CopyTextureRegion contract](https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-copytextureregion).
Earlier depth-stencil rejection notes are superseded for these tested formats.
Four six-frame real-shim runs cover D32 float, R32 typeless, D32S8 typed and
D32S8 typeless. All outputs remain finite/nonuniform and byte-identical to
standalone replay. D32S8 fixtures upload changing stencil sentinels to plane 1
and independently read them back after RR; every stencil byte is preserved.
The runs retain optional-guide toggles, inherited access and final-bound
Release-before-submit coverage; D32S8 also exercises enhanced access.
Evidence: `build/rr-ngx-depth-{d32,r32-typeless,d32s8,d32s8-typeless}-v1/summary.json`.
A further normalized, inverted hardware-depth sequence passes six exact frames
through typeless D32S8, with optional guides, enhanced/inherited access, changing
required formats, AutoExposure and no configured SR dependencies. Stencil
sentinels remain exact. Final report verifies all six hardware-depth/inversion
settings and records creation flags 75. Evidence:
`build/rr-ngx-inverted-hardware-depth-v2/summary.json` and logs. The final harness
also checks that cropped depth-stencil evaluation is rejected.
These are same-engine synthetic comparisons, not a game or NVIDIA quality
reference. D24 conversion, MSAA/array depth, cropped depth-stencil processing,
output conversion, broader encodings, dynamic geometry/concurrency/game
integration, FSR execution and performance parity remain incomplete.
## Player configuration without launcher overrides (2026-10-16)
`d4r.ini` now accepts `[RayReconstruction] Backend = off|native|fsr` and
`ModelDir = auto|PATH`. Off is the default and clears stale RR selection in
player mode. Native resolves relative model paths beside d4r.ini; auto selects
`engine/rr-d`. FSR selection remains explicit but unavailable, with a diagnostic
note and rejected RR requests. The existing readiness check still loads the
configured model before advertising native RR. The source package README/INI
describe these experimental settings. Existing delivered ZIP artifacts have
not been rebuilt or changed; install.bat does not generate RR models yet.
Pre-Init feature-13 requirements now establish player configuration before
checking readiness. An isolated staged player-folder test removes all launcher
path/RR overrides, selects the backend/model through the actual INI, and passes
six exact native RR hardware-depth frames. It also verifies capability contracts,
optional-guide toggling, enhanced/inherited state, inverted D32S8 and stencil
preservation, with final Release-before-submit. Evidence:
`build/rr-ngx-player-config-v1/summary.json` and the staged player's log/INI.
No game folder was read or modified.
The player parser/environment/pin probe passes all checks, including relative RR
paths, invalid backend fallback, FSR unavailability and stale-environment cleanup.
Evidence fixtures: `build/player-config-rr-v2/`. The PowerShell checker recognizes
the new keys and passes static parser syntax validation; its fixture suite was
not rerun under the current script execution policy. No policy bypass was used.
## Local package v4 architecture-loader gate (2026-10-16)
A new development ZIP refreshes the previously verified v3 runtime with the
current shim and INI/checker/installer documentation. The base and all output
manifest hashes verify; local modified provenance remains explicit. No NVIDIA
binaries, RR models or private optimized kernels are bundled. Existing ZIPs
remain unchanged. See `windows-local-package.md` for its hash and size.
The actual packaged architecture loader and refreshed target shim pass six
exact native RR hardware-depth frames with INI-only selection, optional guides,
enhanced/inherited states, stencil preservation and final Release-before-submit.
Pinned SR/core dependencies were staged separately for the test, and the local
RR model remains outside the ZIP. Evidence:
`build/rr-ngx-local-v4-loader-v1/summary.json`. No game, installer execution,
system change, commit or publishing occurred. Automatic RR model provisioning,
game integration, FSR execution and performance parity remain open.
The native interop probe rebuilt successfully after these changes. The requested
camera/reset regression wrapper was blocked by the current PowerShell execution
policy before running. No policy change, bypass, alternate launcher, or probe
retry followed. Runtime regression validation of this refactor remains pending;
the earlier results below describe the preceding build.
The RX 7700 XT now passes cross-API memory/fence transfers and actual RR model-D
inference through native D3D12 textures and the existing Vulkan engine. The NGX
feature-13 game adapter remains unfinished. The probe loads locally extracted
model files, not NVIDIA runtime DLLs, and does not touch a game.
## What is verified
`d4r_rr_interop_probe` matches the D3D12 adapter and Vulkan device by LUID. It
creates a committed shared D3D12 buffer, imports it into a dedicated Vulkan
allocation, and imports a shared D3D12 fence as a Vulkan timeline semaphore.
Each iteration queues this sequence:
1. D3D12 uploads a new deterministic pattern and signals the shared fence.
2. Vulkan waits on that GPU fence, reads the buffer, writes new output, and
signals the same fence at a greater value.
3. D3D12 queues its own wait for Vulkan, copies the output for validation and
signals completion. Only then does the CPU validate both directions.
The internal API handoff uses GPU synchronization and shared memory. CPU uploads
initialize synthetic inputs; CPU readbacks validate results. No tensor passes
through CPU memory between the two APIs.
The image cases also copy the input buffer into a sampled/storage-capable
RGBA16F Vulkan image, preserve its exact input pixels through image readback,
clear it to known half-float values, and copy that output back to the shared
buffer for D3D12 consumption. The padded case verifies untouched row-padding
sentinels separately from image pixels.
| Case | Shared bytes | Iterations |
|---|---:|---:|
| Buffer-only smoke | 1,048,576 | 8 |
| 1712x960 RGBA16F, 13,824-byte row pitch | 13,271,040 | 3 |
| 2560x1440 RGBA16F | 29,491,200 | 3 |
All 14 iterations pass exact comparisons in both directions on native Windows.
The last two cases exercise Vulkan texture transfers. The probe uses explicit
external queue ownership transitions and transfer/host memory barriers.
The imported resource requires dedicated allocation on this driver.
The timeline import follows the [Vulkan D3D12 fence guidance](https://docs.vulkan.org/refpages/latest/refpages/source/VkD3D12FenceSubmitInfoKHR.html).
## Reproduce
```powershell
cmake --build build/windows-rdna4-therock-gfx1101 --target d4r_rr_interop_probe
./scripts/windows/test-rr-interop.ps1 `
-Probe build/windows-rdna4-therock-gfx1101/d4r_rr_interop_probe.exe `
-OutputDirectory build/my-rr-interop-check
```
Use a new output directory under ignored `build/`. Each case has a 30-second
process limit. GPU completion waits have five-second limits. No driver, TDR,
security, or tuning setting is changed.
Evidence: `build/rr-interop-canonical-images-v2/summary.json` and per-case logs.
Earlier buffer-only evidence remains under `build/rr-interop-realistic/`.
## Bridged model inference
`rr_bridge_replay.h` packs all seven canonical inputs into one shared GPU packet.
The harness uploads synthetic data into real D3D12 guide textures, copies them to
the shared buffer, and queues its producer fence. Vulkan imports that memory,
copies each guide into its sampled image and runs `RModel`/`REngine` with the
fixture's matrices, jitter, exposure and reset settings. It copies the RGBA16F
result back to the packet and signals the shared timeline. D3D12 waits on the
GPU, copies the result into its UAV-capable output texture, then reads that
texture back for QA. There is no CPU tensor transfer between APIs.
Actual model-D results, all byte-identical to standalone Vulkan replay:
| Fixture | Render/output | Frames |
|---|---|---:|
| Initial history/reset | 64x64 / 128x128 | 3 |
| Camera translation, jitter, exposure, reset at frames 0 and 3 | 64x64 / 128x128 | 6 |
| Full output size | 853x480 / 2560x1440 | 2 |
All 11 textured frames are finite and nonuniform. The bridge wrapper also
compares output SHA256 independently after the executable's byte comparison.
Reference images come from the same existing RR engine; this proves bridge
consistency, not correctness against NVIDIA or image quality in a game.
```powershell
./scripts/windows/test-rr-bridge-model.ps1 `
-Probe build/windows-rdna4-therock-gfx1101/d4r_rr_interop_probe.exe `
-ModelDirectory build/native-rr-d-windows-gfx1101-final `
-InputDirectory build/rr-1440-inputs `
-ReferenceDirectory build/rr-1440-reference `
-OutputDirectory build/my-rr-model-bridge
```
The process limit is 180 seconds, including pipeline creation. Evidence is under
`build/rr-bridge-d-texture-inference/`, `build/rr-bridge-camera-reset/`, and
`build/rr-bridge-1440/`. The original 14-iteration transfer gate also passes after
the model-mode changes (`build/rr-interop-after-model-mode/`).
The two standalone 1440p reference frames took 29.0 and 38.6 ms of GPU time. These
are two synthetic samples, not a steady-state measurement or Linux parity proof.
No inference timing is attributed to the bridge itself yet.
The harness accepts canonical D/E gfx11 models and the seven required guides.
It rejects optional guide files, duplicate frame IDs and nonfinite settings;
support for optional hit distances, masks, particles and game format conversion
still needs implementation. Feature-13 creation/evaluation/lifetime integration,
game-facing resource state contracts, FSR RR and performance parity remain open.
+162
View File
@@ -0,0 +1,162 @@
# Local Windows RR model cache
## Native Python builder without Git Bash
`tools/windows/build_rr_model.py` now runs the extraction, template generation,
weight packing/widening, glslang compilation and exact-conversion passes directly
on Windows. It uses the same existing model scripts and shader recipe as the
Linux shell, with pinned RR DLL/glslang hashes and exact gfx11 arithmetic.
Python, NumPy and glslang must already be provisioned; automatic dependency
download and install.bat integration remain unfinished.
```powershell
python tools/windows/build_rr_model.py `
--dll build/install-bat-cache/nvidia/nvngx_dlssd.dll `
--preset D --output build/my-native-rr-d
```
Use a fresh ignored build/ directory. `--reuse` verifies the DLL, compiler,
Python, NumPy version, source hashes and every top-level model product before
returning. Missing, modified or wrong-preset caches fail without rebuilding in
place. Each child step has a 600-second limit; a timed-out Windows process tree
is terminated. The caller's environment is copied rather than changed. No Bash,
PowerShell build wrapper, game access, driver change or policy bypass is used.
Derived shader/weight products stay local and must not be bundled.
Actual preset-D build evidence: `build/native-rr-d-python-native-v2/`.
65 of 66 common top-level artifacts match the prior validated Windows model
byte-for-byte, including every shader, weight and argument template. The sole
manifest difference selects the Linux recipe's 64-wide output subgroup instead
of the prior Windows model's 32. Six native NGX hardware-depth frames still
match the old standalone reference exactly, with optional guides, enhanced/
inherited access, format changes, stencil preservation and no SR dependencies.
No speed improvement is claimed for that subgroup setting.
Cache reuse took approximately 0.26 seconds. Wrong-preset reuse and a modified
weight in a separate copied fixture were rejected. The valid model remained
unchanged. Evidence: `build/rr-native-python-builder-validation.json` and
`build/rr-ngx-python-native-model-v1/summary.json`. Preset E validation is recorded
below. Earlier v1/v2 artifacts
are preserved; v1 also differed in manifest line endings, corrected in v2.
## Preset E build and verified local deployment
The native Python E build completed all 48 steps, including its 4096-byte noise
prefix. As with D, 65/66 common top-level artifacts match the prior E model;
the manifest selects the Linux output subgroup width. Six NGX hardware-depth
frames match the prior E standalone engine reference exactly, with optional
guides, format switching, enhanced/inherited access, stencil preservation and
no configured SR dependencies. E cache reuse also passes.
Evidence: `build/rr-native-python-e-comparison.json` and
`build/rr-ngx-python-native-e-model-v1/summary.json`.
`tools/windows/deploy_rr_model.py` verifies the native Python cache's product
hashes and complete D/E manifest, copies a private model into a versioned runtime
`engine/rr-<preset>-<cache hash>` directory, and verifies every copied file.
Existing models are preserved. Repeated deployment reuses identical products;
changed products fail instead of replacing an existing model. No compiler or
NVIDIA DLL is copied. With `--enable`, it backs up d4r.ini outside the game/runtime
parent, then selects native RR and the relative model path. Comments, BOM,
line endings, SR settings and unrelated sections are preserved.
```powershell
python tools/windows/deploy_rr_model.py --model build/my-native-rr-d `
--runtime C:/Games/Example/d4r --enable
```
Only ignored mock-runtime paths were used in validation. An actual E deployment
verified 66 products, preserved an exact INI backup, and repeated without further
configuration changes/backups. A tampered model was rejected with every runtime
file unchanged. Three CPU tests cover BOM/CRLF/comments, absent/duplicate RR
sections and idempotence. The deployed model then passed six exact RR frames
through the packaged architecture loader using the preserved, deployment-written
relative INI path. Evidence: `build/rr-model-deploy-validation.json`,
`build/rr-model-deploy-negative-validation.json`, and
`build/rr-ngx-deployed-e-ini-v1/summary.json`.
This automates local model deployment and selection. Dependency provisioning and
install.bat integration remain unfinished; the tools are not added to v4 ZIP.
No game was accessed, source committed, release published or system policy changed.
## Portable dependency bundle
`tools/windows/prepare_rr_tools.py` downloads and verifies pinned archives for
[Python 3.12.10 embeddable x64](https://www.python.org/downloads/release/python-31210/),
[NumPy 2.4.6 cp312 Windows x64](https://pypi.org/project/numpy/2.4.6/), and
glslang 16.6.0. It copies the tracked model-build sources, the kernel allowlist,
and license texts into a fresh local bundle. Python runs isolated with explicit
bundle-relative imports; system Python, pip and Git Bash are not needed to build
or deploy models from the prepared bundle. No system Python installation, PATH,
driver, security policy or game folder is changed.
```powershell
python tools/windows/prepare_rr_tools.py --output build/my-rr-tools `
--archive build/my-rr-tools.zip
build/my-rr-tools/python/python.exe `
build/my-rr-tools/recipe/tools/windows/build_rr_model.py `
--dll build/install-bat-cache/nvidia/nvngx_dlssd.dll --preset D `
--output build/my-rr-tools/recipe/build/rr-d
```
Prepared local archive: `build/rr-portable-tools-v2.zip`, 40,154,515 bytes;
SHA256 `297289a57a82142eb952479c99112b1c352a6753eb15bc6b2431bf686aed9b22`.
All 1,111 recorded files verify. The archive is created from the pristine tool
manifest before model generation and contains no NVIDIA DLL, extracted PTX,
SPIR-V model shader or generated model. Building inside the extracted tool
directory later creates private products there; those products must not be
added to an archive. The recorded clean tools ZIP remains unchanged.
Actual isolated-bundle D build completed all 48 steps. All 66 top-level model
artifacts match the already executed native Python D model byte-for-byte.
Bundled NumPy loaded from the bundle's own vendor directory, and cache reuse
passes. Bundled Python also deployed the verified 66-product model into a fresh
ignored mock runtime, selected it through INI and preserved an exact external
configuration backup. Evidence: `build/rr-portable-tools-validation.json` and
`build/rr-portable-deployment-validation.json`.
The first bundle lacked `native_kernels.txt`; its build failed before weight
extraction and remains preserved separately. V2 includes that allowlist. Both
cache implementations now fingerprint source `.txt` inputs too. Existing cache
stamps made before this change may reject reuse; their artifacts are preserved,
and rebuilding uses a fresh output directory. No cache was silently restamped.
Dependency preparation and portable model build/deployment are verified.
Connecting the tool bundle to install.bat and the main runtime ZIP remains open,
along with game integration, FSR execution and performance parity.
## Existing PowerShell/Git Bash workflow
The native D/E builder can reuse a verified model directory instead of rebuilding
all extracted weights and shaders. This is local build automation; it does not
capture game inputs or implement the Windows RR game adapter.
```powershell
./scripts/windows/build-native-rr-model.ps1 `
-DlssdDll build/install-bat-cache/nvidia/nvngx_dlssd.dll `
-Preset D -OutputDirectory build/native-rr-d-cache-verified -ReuseCache
```
Use the path to your own installer-staged, pinned DLL. On a new output directory,
the builder compiles the model and writes `windows-rr-cache.json`. On an existing
directory, `-ReuseCache` checks the manifest and returns only if every input and
product hash still matches. Missing, modified or stale caches fail; select a new
directory to rebuild. Legacy directories without a cache manifest are not
silently trusted or overwritten.
The cache records the DLL, Python, Git Bash and glslang hashes; NumPy version;
source hashes; preset; gfx1101 architecture; exact arithmetic; and hashes of all
top-level generated model artifacts. It also checks the RR manifest preset,
arithmetic and complete 12-stage shader/weight references. All derived model data
stays under ignored `build/`. The cache is not a public package artifact.
Validation on the real locally built preset D model:
- Full build completed and stamped 66 artifacts.
- Verified reuse took approximately 0.64 seconds and did not rewrite the shader.
- Flipping a byte in a weight file caused rejection; the original file was
restored and its SHA256 rechecked before a final successful cache hit.
- Requesting E against the D model was rejected.
- The builder restored the caller's `D4R_RR_GFX11` and `D4R_RR_RELAXED` settings.
Evidence: `build/rr-cache-validation.json`. These checks establish cache behavior,
not RR quality, game support, or a performance improvement in inference.
+13 -5
View File
@@ -4,9 +4,10 @@ d4r for Windows: NVIDIA DLSS on AMD Radeon RDNA3 and RDNA4
d4r runs NVIDIA's own DLSS (DLSS 4 by default) on a Radeon GPU in DirectX 12 games. OptiScaler
(included) catches the game's upscaler calls and hands them to d4r.
This is an early release. Only the Radeon RX 9070 XT has been tested on real hardware; every other
GPU in this release is compile-tested. SUPPORTED_GAMES.md in the source repository lists the
tested games. Expect problems.
This is an early release. RX 7700 XT has passed native Windows synthetic checks and limited
game-frame tests. RX 9070 XT also has recorded hardware tests; other targets are compile-tested.
These checks do not establish broad game support or Linux performance parity.
SUPPORTED_GAMES.md in the source repository lists the tested games. Expect problems.
What you need
-------------
@@ -28,7 +29,8 @@ Install
2. Run install.bat and accept the download prompt. Existing d4r DLLs are backed up outside the game
folder with their original paths and SHA256 hashes. Verified downloads are cached in
%LOCALAPPDATA%\d4r\cache.
The Ray Reconstruction DLL is local preparation only; this Windows game backend does not run RR yet.
The Ray Reconstruction DLL alone does not enable RR. Experimental native RR requires a locally
generated D/E model and explicit d4r.ini selection; game compatibility remains unverified.
3. Optional: check the install. In the game folder, open PowerShell and run
powershell -ExecutionPolicy Bypass -File d4r\d4r-check.ps1
4. Start the game from Steam, Epic or a shortcut as usual. In its graphics settings choose DLSS
@@ -44,6 +46,10 @@ Edit d4r\d4r.ini (it is commented) and restart the game. Each game has its own c
- Model: E (preset 5), K (11, DLSS 4), L (12) or M (13, DLSS 4.5). E and L set NGX hints only; the standalone engine and L texture kernels are not integrated. Select quality mode in the game.
- Kernels/NativeFp8: experimental RDNA4 setting. Requires a matching gfx12 native-FP8 M bundle; keep false with the standard FP16 release. Token-lane and hardware-pack builds remain unvalidated on GPU.
- AsyncInterop, ValidateOutput, Log: for testing and bug reports; the defaults are the tested setting.
- RayReconstruction/Backend: off by default. native selects experimental native D3D12/Vulkan RR;
ModelDir selects your locally generated D/E model (auto: d4r\engine\rr-d). Relative paths resolve
beside d4r.ini. No model is bundled or automatically generated by install.bat yet.
fsr currently rejects RR requests because native Windows FSR RR execution is unavailable.
OptiScaler's own settings (overlay, render ratios) are in OptiScaler.ini.
If something goes wrong
@@ -53,7 +59,9 @@ If something goes wrong
- DLSS missing in the menu and no d4r_nvngx.log: the files are not next to the game's main .exe.
- For a bug report run d4r\d4r-check.ps1 -Report and send the ZIP it puts on your Desktop,
with your GPU, the game, the model and what the image looked like. Logs can contain folder names.
- Not supported: frame generation, standalone inference engine, DLSS Ray Reconstruction, DirectX 11, Vulkan. E/L preset hints do not establish full Windows feature parity.
- Not supported: frame generation, standalone inference engine selection in the game, DirectX 11,
Vulkan game integration, or FSR RR. Native DLSS RR has synthetic feature-13 validation only;
it does not establish game compatibility or NVIDIA reference quality. E/L hints do not establish full parity.
Uninstall
---------
+12 -1
View File
@@ -2,7 +2,7 @@
; this game only. Edit it with Notepad, save, and restart the game. Mistakes are reported in
; d4r\d4r_nvngx.log and by d4r-check.ps1; an invalid value falls back to the default shown here.
;
; Engine inference and Ray Reconstruction are not yet integrated in this Windows build.
; Native Ray Reconstruction is experimental and requires a locally generated D/E model.
[DLSS]
; DLSS model:
@@ -40,8 +40,19 @@ Log = normal
; Folder for the compiled DLSS kernels. auto = %LOCALAPPDATA%\d4r\cache. The first launch fills it
; and can take several minutes; later launches are quick.
CacheDir = auto
; Optional complete private native kernel directory, generated locally from your DLLs.
; auto uses the packaged kernels. Relative paths resolve beside this d4r.ini.
NativeDir = auto
[Kernels]
; Experimental RDNA4 only. Requires a matching token-lane FP8 M bundle; startup refuses this
; setting if d4r/native/<gfx12>/d4r-native-fp8.txt is absent. Default keeps the FP16 path.
NativeFp8 = false
[RayReconstruction]
; off is the default. native uses the local Vulkan RR engine through native D3D12.
; fsr is currently unavailable and rejects RR requests. No fallback to noisy upscaling.
Backend = off
; auto selects d4r/engine/rr-d. Relative paths resolve beside this d4r.ini.
; Generate the D/E model locally; model data is not bundled in the ZIP.
ModelDir = auto
+11 -5
View File
@@ -4,12 +4,17 @@ param([ValidateSet('enc0','enc1','enc2','enc3','enc4','dec5','dec4','dec3','dec2
[ValidateSet('baseline','load','store','both')][string]$NonTemporal='baseline',
[ValidateSet(0,1,2,4)][int]$PosTiles=0, [switch]$ShareValues, [switch]$PrivateValues,
[switch]$PackedAccumulator,
[ValidateSet('gfx1100','gfx1101','gfx1102','gfx1103','gfx1150','gfx1151','gfx1152','gfx1153','gfx1154','gfx1200','gfx1201')]
[string]$GpuArch='gfx1201',
[string]$HipRoot, [string]$OutputDirectory)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
if ($ShareValues -and $PrivateValues) { throw 'Choose shared or private V storage.' }
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo "build/native-k-experiment-$Layer-$Mode-$NonTemporal-pos$PosTiles-share$([int][bool]$ShareValues)-private$([int][bool]$PrivateValues)-packed$([int][bool]$PackedAccumulator)" }
if ($GpuArch -notmatch '^gfx12' -and ($PosTiles -or $ShareValues -or $PrivateValues -or $PackedAccumulator)) {
throw 'Token tiling and accumulator/storage experiments require gfx12.'
}
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo "build/native-k-experiment-$GpuArch-$Layer-$Mode-$NonTemporal-pos$PosTiles-share$([int][bool]$ShareValues)-private$([int][bool]$PrivateValues)-packed$([int][bool]$PackedAccumulator)" }
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
foreach ($baseline in @('build/native-k-gfx1201','build/windows-rdna4-therock','dist/windows-rdna4-command-list')) {
if ($OutputDirectory.StartsWith([IO.Path]::GetFullPath((Join-Path $repo $baseline)), [StringComparison]::OrdinalIgnoreCase)) {
@@ -40,22 +45,23 @@ if ($ShareValues) {
}
if ($PackedAccumulator) { $flags+='-DD4R_K_PACKED_ACC' }
if ($PrivateValues) { $flags+='-DD4R_K_PRIVATE_V' }
if ($GpuArch -match '^gfx11') { $flags+='-DD4R_WINDOWS_GFX11_LANE_EXCHANGE' }
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
$object=Join-Path $OutputDirectory "$name.hsaco"
$arguments=@('-x','hip','-std=c++17','--offload-arch=gfx1201','--offload-device-only',
$arguments=@('-x','hip','-std=c++17',"--offload-arch=$GpuArch",'--offload-device-only',
'--no-gpu-bundle-output','-mno-wavefrontsize64','-nogpuinc','-nogpulib','-O3',
'-DD4R_DEVICE_ONLY_MINIMAL','-DD4R_K_FP16_BASELINE')+$flags+@($source,'-o',$object)
$ErrorActionPreference='Continue'
& $compiler @arguments > (Join-Path $OutputDirectory "$name.build.stdout.log") 2> (Join-Path $OutputDirectory "$name.build.stderr.log")
$ErrorActionPreference='Stop'
if ($LASTEXITCODE) { throw "Experimental K compile failed: $Layer" }
& (Join-Path (Split-Path $compiler) 'llvm-objdump.exe') --disassemble --mcpu=gfx1201 $object `
& (Join-Path (Split-Path $compiler) 'llvm-objdump.exe') --disassemble "--mcpu=$GpuArch" $object `
> (Join-Path $OutputDirectory "$name.isa.txt")
if ($LASTEXITCODE) { throw 'Native object ISA extraction failed.' }
& (Join-Path (Split-Path $compiler) 'llvm-readobj.exe') --notes $object `
> (Join-Path $OutputDirectory "$name.metadata.txt")
if ($LASTEXITCODE) { throw 'Native object metadata extraction failed.' }
@{layer=$Layer; architecture='gfx1201'; experimental=$true; numericalValidation='pending';
@{layer=$Layer; architecture=$GpuArch; experimental=$true; numericalValidation='pending';
mode=$Mode; nonTemporal=$NonTemporal; tokenTilesPerWave=$PosTiles; shareValues=[bool]$ShareValues;
privateValues=[bool]$PrivateValues;
packedAccumulator=[bool]$PackedAccumulator;
@@ -64,5 +70,5 @@ if ($LASTEXITCODE) { throw 'Native object metadata extraction failed.' }
compilerVersion=(& $compiler --version | Out-String).Trim();
sha256=(Get-FileHash -LiteralPath $object -Algorithm SHA256).Hash} |
ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $OutputDirectory "$name.build-info.json") -Encoding UTF8
Write-Host "Compiled gfx1201 K experiment: $Layer mode=$Mode nonTemporal=$NonTemporal posTiles=$PosTiles shareValues=$([bool]$ShareValues). Validate before installing."
Write-Host "Compiled $GpuArch K experiment: $Layer mode=$Mode nonTemporal=$NonTemporal posTiles=$PosTiles shareValues=$([bool]$ShareValues). Validate before installing."
# No override manifest or game installation is produced by this script.
+18 -4
View File
@@ -3,7 +3,8 @@ param(
[Parameter(Mandatory=$true)][string]$DlssdDll,
[ValidateSet('D','E')][string]$Preset='D',
[string]$OutputDirectory,
[string]$GitBash='C:\Program Files\Git\bin\bash.exe'
[string]$GitBash='C:\Program Files\Git\bin\bash.exe',
[switch]$ReuseCache
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
@@ -18,7 +19,7 @@ $buildRoot=[IO.Path]::GetFullPath((Join-Path $repo 'build'))
if(!$OutputDirectory.StartsWith($buildRoot+'\',[StringComparison]::OrdinalIgnoreCase)){
throw "RR model output must stay under the ignored build directory: $OutputDirectory"
}
if(Test-Path -LiteralPath $OutputDirectory){throw "Use a new RR model output directory: $OutputDirectory"}
if((Test-Path -LiteralPath $OutputDirectory) -and !$ReuseCache){throw "Use a new RR model output directory or -ReuseCache: $OutputDirectory"}
$glslang=Join-Path $repo '.tools/glslang-16.6.0/bin'
$pythonHome=Split-Path (Get-Command python.exe -ErrorAction Stop).Source
$pythonShim=Join-Path $repo '.tools/rr-build-bin'
@@ -35,6 +36,8 @@ $unixOut=(& $cygpath -u $OutputDirectory).Trim()
if(!$unixRepo -or !$unixDll -or !$unixOut){throw 'Git Bash path conversion failed.'}
$oldPath=$env:PATH
$oldPythonPath=$env:PYTHONPATH
$oldGfx11=$env:D4R_RR_GFX11
$oldRelaxed=$env:D4R_RR_RELAXED
$env:PATH="$pythonShim;$pythonHome;$glslang;$(Split-Path $GitBash -Parent);$oldPath"
$env:PYTHONPATH=$pythonVendor
$env:D4R_RR_GFX11='1'
@@ -43,7 +46,16 @@ $log=Join-Path $buildRoot ("native-rr-$($Preset.ToLower())-windows-build.log")
try{
& $python3 -c 'import numpy; print("numpy", numpy.__version__)'
if($LASTEXITCODE){throw 'Pinned NumPy could not load in the Windows RR build environment.'}
$command="cd '$unixRepo' && D4R_RR_GFX11=1 D4R_RR_RELAXED=0 bash engine/rr/build_native_model.sh --dll '$unixDll' '$Preset' '$unixOut'"
$cacheArguments=@('--model',$OutputDirectory,'--dll',$DlssdDll,'--preset',$Preset,
'--glslang',(Join-Path $glslang 'glslangValidator.exe'),'--bash',$GitBash)
$cacheTool=Join-Path $PSScriptRoot 'rr-model-cache.py'
if(Test-Path -LiteralPath $OutputDirectory){
& $python3 $cacheTool check @cacheArguments
if($LASTEXITCODE){throw 'RR model cache is absent, stale or changed. Use a new output directory.'}
return
}
function Quote-Bash([string]$Value){return "'"+$Value.Replace("'","'\''")+"'"}
$command="cd $(Quote-Bash $unixRepo) && D4R_RR_GFX11=1 D4R_RR_RELAXED=0 bash engine/rr/build_native_model.sh --dll $(Quote-Bash $unixDll) '$Preset' $(Quote-Bash $unixOut)"
$previousPreference=$ErrorActionPreference
try{
$ErrorActionPreference='Continue'
@@ -54,6 +66,8 @@ try{
foreach($required in @('rr.txt','model.json','enc0_m.spv','dec0_m.spv','hkpn.spv','enc0.args','hkpn.args')){
if(!(Test-Path -LiteralPath (Join-Path $OutputDirectory $required))){throw "RR build incomplete; missing $required"}
}
& $python3 $cacheTool write @cacheArguments
if($LASTEXITCODE){throw 'RR build completed but cache manifest could not be written.'}
Write-Host "Built local preset $Preset native RR model: $OutputDirectory"
Write-Host "DLL SHA256: $hash. Build log: $log"
}finally{$env:PATH=$oldPath;$env:PYTHONPATH=$oldPythonPath}
}finally{$env:PATH=$oldPath;$env:PYTHONPATH=$oldPythonPath;$env:D4R_RR_GFX11=$oldGfx11;$env:D4R_RR_RELAXED=$oldRelaxed}
+28 -5
View File
@@ -1,14 +1,26 @@
[CmdletBinding()]
param([string]$SourceRoot, [string]$HipRoot = 'C:\Program Files\AMD\ROCm\7.2', [string]$WorkRoot)
param([string]$SourceRoot, [string]$HipRoot = 'C:\Program Files\AMD\ROCm\7.2', [string]$WorkRoot, [string]$LlvmBuildRoot)
$ErrorActionPreference = 'Stop'
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
if (!$WorkRoot) { $WorkRoot = Join-Path $repo 'build/zluda-helpers-windows' }
$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'
$compiler = Join-Path $HipRoot 'bin/clang++.exe'
$dis = Join-Path $HipRoot 'bin/llvm-dis.exe'
$assembler = Join-Path $HipRoot 'bin/llvm-as.exe'
$ocml = Join-Path $HipRoot 'amdgcn/bitcode/ocml.bc'
function Find-HipHelperTool([string]$Name) {
foreach ($folder in @('lib/llvm/bin','bin')) {
$candidate=Join-Path $HipRoot "$folder/$Name.exe"
if(Test-Path -LiteralPath $candidate){return $candidate}
}
throw "HIP helper tool missing: $Name under $HipRoot"
}
$compiler = Find-HipHelperTool 'clang++'
$dis = Find-HipHelperTool 'llvm-dis'
$assembler = Find-HipHelperTool 'llvm-as'
if(!$LlvmBuildRoot){$LlvmBuildRoot=Join-Path $repo 'build/zluda-llvm-windows-gnu'}
$pinnedAssembler=Join-Path $LlvmBuildRoot 'bin/llvm-as.exe'
if(!(Test-Path -LiteralPath $pinnedAssembler)){throw 'Build the pinned LLVM llvm-as target before the PTX helpers.'}
$assembler=$pinnedAssembler
$ocml = Join-Path $HipRoot 'lib/llvm/amdgcn/bitcode/ocml.bc'
if(!(Test-Path -LiteralPath $ocml)){$ocml=Join-Path $HipRoot 'amdgcn/bitcode/ocml.bc'}
foreach ($file in @($compiler,$dis,$assembler,$ocml)) {
if (!(Test-Path $file)) { throw "HIP helper dependency missing: $file" }
}
@@ -58,13 +70,24 @@ foreach ($variant in $variants) {
if ($LASTEXITCODE) { throw "Disassemble failed: $raw" }
$clean = [Collections.Generic.List[string]]::new()
foreach ($line in [IO.File]::ReadLines($ir)) {
# Directly linked helpers do not need a ThinLTO index, which may retain
# references to the stripped HIP/LLVM globals.
if ($line -match '^\^\d+\s*=') { continue }
if ($line -match '@llvm.used|wchar_size|llvm.module.flags|__hip_cuid') { continue }
$line = $line -replace 'optnone','' -replace 'define hidden','define linkonce_odr'
$line = $line -replace '"target-cpu"="gfx1030"','' -replace '"target-features"="[^"]+"',''
$clean.Add($line)
}
[IO.File]::WriteAllLines($ir,$clean,[Text.UTF8Encoding]::new($false))
if($assembler -eq $pinnedAssembler){
& python (Join-Path $repo 'tools/windows/llvm22_ir.py') $ir $ir
if($LASTEXITCODE){throw 'LLVM 22 helper IR compatibility conversion failed.'}
}
$previousPath=$env:PATH
$env:PATH="$(Join-Path $toolchain 'bin');$previousPath"
try {
& $assembler $ir -o $output
if ($LASTEXITCODE) { throw "Assemble failed: $ir" }
} finally {$env:PATH=$previousPath}
Write-Host "Built $output ($((Get-Item $output).Length) bytes)"
}
+1 -1
View File
@@ -31,7 +31,7 @@ try {
$ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "LLVM configure failed ($LASTEXITCODE)" }
$ErrorActionPreference = 'Continue'
& $cmake --build $BuildRoot --parallel $Jobs --target llvm-config llvm-libraries lldCommon lldELF `
& $cmake --build $BuildRoot --parallel $Jobs --target llvm-config llvm-libraries lldCommon lldELF llvm-as `
2>&1 | Tee-Object (Join-Path $BuildRoot 'build.log')
$ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "LLVM build failed ($LASTEXITCODE)" }
+26 -13
View File
@@ -1,10 +1,10 @@
[CmdletBinding()]
param([string]$SourceRoot, [string]$LlvmBuildRoot, [string]$InstallRoot, [switch]$FetchOnly, [switch]$CacheTestsOnly, [switch]$CompilerTestsOnly, [int]$Jobs = 4)
param([string]$SourceRoot, [string]$LlvmBuildRoot, [string]$InstallRoot, [switch]$FetchOnly, [switch]$CacheTestsOnly, [switch]$CompilerTestsOnly, [switch]$EmitterOnly, [int]$Jobs = 4)
$ErrorActionPreference = 'Stop'
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' }
if (!$LlvmBuildRoot) { $LlvmBuildRoot = Join-Path $repo 'build/zluda-llvm-windows-gnu' }
if (!$InstallRoot) { $InstallRoot = Join-Path $repo 'dist/zluda-windows-final' }
if (!$InstallRoot) { $InstallRoot = Join-Path $repo $(if ($EmitterOnly) { 'dist/zluda-windows-emitter' } else { 'dist/zluda-windows-final' }) }
$rust = Join-Path $repo '.tools/rust-1.98.1-gnu'
$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64'
$target = Join-Path $repo 'build/zluda-rust-windows-gnu'
@@ -60,28 +60,41 @@ try {
foreach ($variant in @('','_constrained','_w64','_constrained_w64')) {
if (!(Test-Path "ptx/lib/zluda_ptx_impl$variant.bc")) { throw 'Run build-zluda-helpers.ps1 first.' }
}
$ErrorActionPreference = 'Continue'
& $cargo build --locked --release --target x86_64-pc-windows-gnu -p zluda -p nvapi -p zluda_trace `
2>&1 | Tee-Object (Join-Path $target 'build.log')
$ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "ZLUDA build failed ($LASTEXITCODE)" }
if (!$EmitterOnly) {
$ErrorActionPreference = 'Continue'
& $cargo build --locked --release --target x86_64-pc-windows-gnu -p zluda -p nvapi -p zluda_trace `
2>&1 | Tee-Object (Join-Path $target 'build.log')
$ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "ZLUDA build failed ($LASTEXITCODE)" }
}
$ErrorActionPreference = 'Continue'
& $cargo build --locked --release --target x86_64-pc-windows-gnu -p ptx --example d4r_emit `
2>&1 | Tee-Object (Join-Path $target 'emit-build.log')
$ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "d4r_emit build failed ($LASTEXITCODE)" }
$release = Join-Path $target 'x86_64-pc-windows-gnu/release'
Copy-Item -LiteralPath (Join-Path $release 'nvcuda.dll') -Destination $InstallRoot -Force
Copy-Item -LiteralPath (Join-Path $release 'nvapi64.dll') -Destination $InstallRoot -Force
if (!$EmitterOnly) {
Copy-Item -LiteralPath (Join-Path $release 'nvcuda.dll') -Destination $InstallRoot -Force
Copy-Item -LiteralPath (Join-Path $release 'nvapi64.dll') -Destination $InstallRoot -Force
}
# The C++ dependencies select libc++ dynamically even when Rust requests
# static GNU support libraries. Keep these portable runtime DLLs local.
foreach ($runtime in @('libc++.dll', 'libunwind.dll')) {
Copy-Item -LiteralPath (Join-Path $toolchain "bin/$runtime") -Destination $InstallRoot -Force
}
New-Item -ItemType Directory -Force (Join-Path $InstallRoot 'trace') | Out-Null
Copy-Item -LiteralPath (Join-Path $release 'zluda_trace.dll') -Destination (Join-Path $InstallRoot 'trace/nvcuda.dll') -Force
if (!$EmitterOnly) {
New-Item -ItemType Directory -Force (Join-Path $InstallRoot 'trace') | Out-Null
Copy-Item -LiteralPath (Join-Path $release 'zluda_trace.dll') -Destination (Join-Path $InstallRoot 'trace/nvcuda.dll') -Force
}
Copy-Item -LiteralPath (Join-Path $release 'examples/d4r_emit.exe') -Destination $InstallRoot -Force
@{base='ee2f25a180099fa42f36b2346732e1f2470a03ad'; target='x86_64-pc-windows-gnu';
if(Test-Path -LiteralPath (Join-Path $LlvmBuildRoot 'bin/llvm-as.exe')){
Copy-Item -LiteralPath (Join-Path $LlvmBuildRoot 'bin/llvm-as.exe') -Destination $InstallRoot -Force
}
@{base='ee2f25a180099fa42f36b2346732e1f2470a03ad'; target='x86_64-pc-windows-gnu'; emitterOnly=[bool]$EmitterOnly;
emitterSha256=(Get-FileHash -LiteralPath (Join-Path $InstallRoot 'd4r_emit.exe') -Algorithm SHA256).Hash;
assemblerSha256=$(if(Test-Path -LiteralPath (Join-Path $InstallRoot 'llvm-as.exe')){
(Get-FileHash -LiteralPath (Join-Path $InstallRoot 'llvm-as.exe') -Algorithm SHA256).Hash
}else{$null});
sourceCommit=(& git rev-parse HEAD); workingTreeDirty=[bool](& git status --porcelain);
rust='1.98.1'; llvm=(& (Join-Path $LlvmBuildRoot 'bin/llvm-config.exe') --version);
llvmSource=$(if (Test-Path (Join-Path $LlvmBuildRoot 'd4r-llvm-build-info.json')) {
@@ -89,7 +102,7 @@ try {
} else { $null });
files=@(Get-ChildItem -LiteralPath $InstallRoot -Filter '*.dll' | Get-FileHash -Algorithm SHA256)} |
ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallRoot 'build-info.json') -Encoding UTF8
Write-Host "Native Windows d4r ZLUDA runtime: $InstallRoot"
Write-Host "Native Windows d4r ZLUDA $(if ($EmitterOnly) { 'offline emitter' } else { 'runtime' }): $InstallRoot"
} finally {
Pop-Location
foreach ($name in $previous.Keys) { [Environment]::SetEnvironmentVariable($name,$previous[$name],'Process') }
+34 -3
View File
@@ -158,7 +158,8 @@ else {
Ok "GPU $($device.name) ($a): build present, $native native kernels"
if (!$arch) { $arch = $a }
}
if ($a -ne 'gfx1201') { Note "only the RX 9070 XT (gfx1201) has been tested on real hardware; $a is compile-tested" }
if ($a -eq 'gfx1101') { Note 'RX 7700 XT has passed native Windows synthetic checks and limited game-frame tests; broad game validation remains incomplete' }
elseif ($a -ne 'gfx1201') { Note "$a is compile-tested; no native Windows hardware validation is recorded for this target" }
}
}
if (@($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 }).Count -gt 1) { Note 'several GPUs found; d4r uses the one the game renders on' }
@@ -197,7 +198,7 @@ $iniPath = Join-Path $d4r 'd4r.ini'
$model = 'K'
if (Test-Path -LiteralPath $iniPath) {
$section = ''; $iniProblems = 0
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1; 'kernels.nativefp8' = 1 }
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1; 'paths.nativedir' = 1; 'kernels.nativefp8' = 1; 'rayreconstruction.backend' = 1; 'rayreconstruction.modeldir' = 1 }
$boolean = @('dlss.showwatermark', 'interop.asyncinterop', 'debug.validateoutput', 'kernels.nativefp8')
foreach ($raw in (Read-TextFile $iniPath).TrimStart([char]0xFEFF) -split "`n") {
$line = $raw.Trim()
@@ -212,11 +213,41 @@ if (Test-Path -LiteralPath $iniPath) {
elseif ($name -eq 'dlss.model') {
if (@('', 'auto', 'k', 'dlss4', '11') -contains $v) { $model = 'K' }
elseif (@('m', 'dlss4.5', '13') -contains $v) { $model = 'M' }
else { Note "d4r.ini: [DLSS] Model '$value' is not available on Windows (use K or M); K will be used"; $iniProblems++ }
elseif (@('e', '5') -contains $v) { $model = 'E' }
elseif (@('l', '12') -contains $v) { $model = 'L' }
else { Note "d4r.ini: [DLSS] Model '$value' is not available on Windows (use E, K, L or M); K will be used"; $iniProblems++ }
}
elseif ($boolean -contains $name) {
if (@('', 'auto', '1', 'true', 'yes', 'on', '0', 'false', 'no', 'off') -notcontains $v) { Note "d4r.ini: [$section] $key must be true or false, not '$value'; the default will be used"; $iniProblems++ }
}
elseif ($name -eq 'rayreconstruction.backend') {
if ($v -notin @('', 'auto', 'off', 'native', 'fsr')) { Note 'd4r.ini: [RayReconstruction] Backend must be off, native or fsr; off will be used'; $iniProblems++ }
elseif ($v -eq 'fsr') { Note 'Native Windows FSR RR is unavailable; RR requests will be rejected' }
}
elseif ($name -eq 'paths.nativedir' -and $v -notin @('', 'auto')) {
$privateNative=if([IO.Path]::IsPathRooted($value)){$value}else{Join-Path $d4r $value}
if($arch -and (Test-Path -LiteralPath (Join-Path $privateNative $arch))){$privateNative=Join-Path $privateNative $arch}
$privateObjects=@(Get-ChildItem -LiteralPath $privateNative -Filter '*.hsaco' -ErrorAction SilentlyContinue)
if(!$privateObjects.Count -or !(Test-Path -LiteralPath (Join-Path $privateNative 'd4r-kernels.txt'))){
Bad '[Paths] NativeDir must contain the complete native kernel set and d4r-kernels.txt';$iniProblems++
}else{
foreach($requiredNative in @(Get-ChildItem -LiteralPath (Join-Path $d4r "native\$arch") -Filter '*.hsaco' -ErrorAction SilentlyContinue)){
if(!(Test-Path -LiteralPath (Join-Path $privateNative $requiredNative.Name))){
Bad "NativeDir is incomplete; missing $($requiredNative.Name)";$iniProblems++
}
}
$privateTarget=@($registry | Where-Object architecture -eq $arch) | Select-Object -First 1
foreach($object in $privateObjects){
$stream=[IO.File]::OpenRead($object.FullName)
try{$header=New-Object byte[] 64;$read=$stream.Read($header,0,64)}finally{$stream.Dispose()}
if($privateTarget -and ($read -ne 64 -or $header[0] -ne 127 -or $header[1] -ne 69 -or
$header[2] -ne 76 -or $header[3] -ne 70 -or $header[48] -ne $privateTarget.elfMachine)){
Bad "NativeDir $($object.Name) is not built for $arch";$iniProblems++
}
}
if(!$iniProblems){Ok "private native kernel directory: $privateNative"}
}
}
elseif ($name -eq 'debug.log' -and @('', 'auto', 'normal', 'verbose') -notcontains $v) { Note "d4r.ini: [Debug] Log must be normal or verbose, not '$value'; normal will be used"; $iniProblems++ }
}
if (!$iniProblems) { Ok "d4r.ini is valid (model $model)" }
+19 -8
View File
@@ -1,6 +1,6 @@
# Builds the Windows drag-in release: extract it into the game folder, copy
# the two NVIDIA DLLs, edit d4r\d4r.ini. Like the Linux release, no installer
# or launcher is involved. Inputs are the per-target game packages that
# Builds the Windows drag-in release: extract it into the game folder and run
# install.bat for the pinned local NVIDIA dependencies. No game launcher is
# required. Inputs are the per-target game packages that
# package-windows-game.ps1 produces (the same inputs as the quick test).
[CmdletBinding()]
param(
@@ -8,17 +8,27 @@ param(
[Parameter(Mandatory=$true)][string]$LoaderDll,
[Parameter(Mandatory=$true)][string]$PackageRoot,
[string[]]$Architectures, # default: every target in gpu-targets.json; a subset makes a smaller release
[string]$ArchivePath
[string]$ArchivePath,
[switch]$LocalDevelopment
)
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'gpu-target.ps1')
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$GamePackagePrefix) { $GamePackagePrefix=Join-Path $repo 'dist/windows-gpu-coverage-game' }
$PackageRoot=[IO.Path]::GetFullPath($PackageRoot)
if($LocalDevelopment){
$buildRoot=[IO.Path]::GetFullPath((Join-Path $repo 'build'))+'\'
foreach($candidate in @($PackageRoot,$ArchivePath) | Where-Object { $_ }){
if(!([IO.Path]::GetFullPath($candidate)).StartsWith($buildRoot,[StringComparison]::OrdinalIgnoreCase)){
throw 'Local development packages and ZIPs must stay under ignored build/.'
}
}
}
if (Test-Path -LiteralPath $PackageRoot) { throw 'Use a new output directory to avoid mixing releases.' }
$git=Get-Command git -ErrorAction SilentlyContinue
$workingTreeDirty=(!$git -or [bool](& git -C $repo status --porcelain))
$commit=if ($git) { (& git -C $repo rev-parse HEAD).Trim() } else { $null }
if ($ArchivePath -and (!$git -or [bool](& git -C $repo status --porcelain))) { throw 'A public ZIP needs git and committed sources.' }
if ($ArchivePath -and !$LocalDevelopment -and (!$git -or [bool](& git -C $repo status --porcelain))) { throw 'A public ZIP needs git and committed sources.' }
New-Item -ItemType Directory -Force $PackageRoot | Out-Null
$files=[Collections.Generic.List[object]]::new()
function Stage([string]$Source,[string]$Relative) {
@@ -35,7 +45,7 @@ if (!$selected.Count -or ($Architectures -and $selected.Count -ne @($Architectur
foreach ($gpu in $selected) {
$arch=$gpu.architecture; $source="$GamePackagePrefix-$arch"
$metadata=Get-Content -LiteralPath (Join-Path $source 'package.json') -Raw | ConvertFrom-Json
if ($metadata.architecture -ne $arch -or $metadata.d4rWorkingTreeDirty) { throw "Uncommitted or wrong-target input: $source" }
if ($metadata.architecture -ne $arch -or ($metadata.d4rWorkingTreeDirty -and !$LocalDevelopment)) { throw "Uncommitted or wrong-target input: $source" }
if (!$baseline) { $baseline=$metadata }
if ($metadata.dlssSha256 -ne $baseline.dlssSha256 -or $metadata.zludaBuild.sourceCommit -ne $baseline.zludaBuild.sourceCommit) { throw 'Input packages do not use the same validated dependencies.' }
$objects=0
@@ -83,11 +93,12 @@ $placeholder=Join-Path $PackageRoot 'd4r/ngx/PUT _nvngx.dll HERE.txt'
New-Item -ItemType Directory -Force (Split-Path $placeholder) | Out-Null
"Copy NVIDIA's _nvngx.dll into this folder. See WINDOWS_README.txt." | Set-Content -LiteralPath $placeholder -Encoding ASCII
$files.Add(@{path='d4r/ngx/PUT _nvngx.dll HERE.txt'; sha256=(Get-FileHash -LiteralPath $placeholder -Algorithm SHA256).Hash; version=$null})
if (Get-ChildItem -LiteralPath $PackageRoot -Recurse -File | Where-Object { $_.Name -eq 'nvngx_dlss.dll' -or ($_.Name -eq '_nvngx.dll' -and $_.DirectoryName -ne (Join-Path $PackageRoot 'd4r')) }) {
if (Get-ChildItem -LiteralPath $PackageRoot -Recurse -File | Where-Object { $_.Name -in @('nvngx_dlss.dll','nvngx_dlssd.dll') -or ($_.Name -eq '_nvngx.dll' -and $_.DirectoryName -ne (Join-Path $PackageRoot 'd4r')) }) {
throw 'Proprietary NVIDIA binaries must not be in the release.'
}
# Read by d4r-check.ps1: every shipped file's hash, so a damaged or mixed install is reported.
$manifest=[ordered]@{schema=1; kind='windows-drag-in'; packageCommit=$commit; binarySourceCommit=$baseline.d4rCommit;
$manifest=[ordered]@{schema=1; kind='windows-drag-in'; localDevelopment=[bool]$LocalDevelopment;
packageWorkingTreeDirty=[bool]$workingTreeDirty; packageCommit=$commit; binarySourceCommit=$baseline.d4rCommit;
dependencies=@{optiScalerSourceCommit=$baseline.optiScaler.sourceCommit; zludaSourceCommit=$baseline.zludaBuild.sourceCommit; hipRuntime=$baseline.hipRuntime};
targets=$targets; files=$files}
$manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $PackageRoot 'd4r/package.json') -Encoding UTF8
@@ -0,0 +1,57 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$DlssDll,
[Parameter(Mandatory=$true)][string]$NativeDirectory,
[Parameter(Mandatory=$true)][string]$OutputDirectory,
[string]$HipRoot,
[string]$EmitterRoot,
[string]$GpuArch='gfx1101',
[string]$Kernel='hiluma_engine_output_depthreg_mvhi_ldr_max_v2_rel'
)
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'gpu-target.ps1')
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
[void](Get-D4RGpuTargetInfo $GpuArch)
$DlssDll=(Get-Item -LiteralPath $DlssDll).FullName
$NativeDirectory=(Get-Item -LiteralPath $NativeDirectory).FullName
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
$buildRoot=[IO.Path]::GetFullPath((Join-Path $repo 'build'))+'\'
if(!$OutputDirectory.StartsWith($buildRoot,[StringComparison]::OrdinalIgnoreCase)){
throw 'Private generated kernels must stay under ignored build/.'
}
if(Test-Path -LiteralPath $OutputDirectory){throw 'Use a new private cache directory.'}
$expected='3975567b8943c53acce397f2b72380092f84f162d00b0d2c7d08a1025c563983'
if((Get-FileHash -LiteralPath $DlssDll).Hash.ToLowerInvariant() -ne $expected){throw 'Expected pinned DLSS SR 310.9.1.'}
if(!$HipRoot){$HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core'}
if(!$EmitterRoot){$EmitterRoot=Join-Path $repo 'dist/zluda-windows-emitter'}
$objects=@(Get-ChildItem -LiteralPath $NativeDirectory -Filter '*.hsaco' -File)
if($objects.Count -ne 16){throw 'NativeDirectory must contain the baseline 11 K and 5 M objects.'}
foreach($family in @('k','m')){
foreach($source in Get-ChildItem -LiteralPath (Join-Path $repo "kernels/$family") -Filter '*.hip' -File){
if(!(Test-Path -LiteralPath (Join-Path $NativeDirectory ($source.BaseName+'.hsaco')))){
throw "Baseline native directory is incomplete: $($source.BaseName)"
}
}
}
foreach($object in $objects){Assert-D4RCodeObjectTarget $object.FullName $GpuArch}
& (Join-Path $PSScriptRoot 'build-native-texture.ps1') -DlssDll $DlssDll -Kernel $Kernel `
-HipRoot $HipRoot -ZludaRoot $EmitterRoot -GpuArch $GpuArch -OutputDirectory (Join-Path $OutputDirectory 'texture')
if($LASTEXITCODE){throw 'Private texture build failed.'}
$native=Join-Path $OutputDirectory 'native'
New-Item -ItemType Directory -Path $native | Out-Null
foreach($object in $objects){Copy-Item -LiteralPath $object.FullName -Destination $native}
foreach($marker in @('d4r-native-fp8.txt')){
if(Test-Path -LiteralPath (Join-Path $NativeDirectory $marker)){
Copy-Item -LiteralPath (Join-Path $NativeDirectory $marker) -Destination $native
}
}
Copy-Item -LiteralPath (Join-Path $OutputDirectory "texture/$Kernel.hsaco") -Destination $native
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $native $DlssDll
if($LASTEXITCODE){throw 'Private native manifest generation failed.'}
@{schema=1; architecture=$GpuArch; privateNvidiaDerivedCode=$true; nativeDirectory=$native;
dlssSha256=$expected; kernel=$Kernel; validation='built only; validate complete frames before game use';
files=@(Get-ChildItem -LiteralPath $native -File | ForEach-Object {
@{name=$_.Name; sha256=(Get-FileHash -LiteralPath $_.FullName).Hash}
})} | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'cache.json') -Encoding UTF8
Write-Host 'Private cache built; no game or existing configuration changed.'
Write-Host "After validation, set [Paths] NativeDir = $native"
+72
View File
@@ -0,0 +1,72 @@
"""Stamp or verify local RR build artifacts against sources, tools and DLL identity."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('action', choices=('write', 'check'))
parser.add_argument('--model', type=Path, required=True)
parser.add_argument('--dll', type=Path, required=True)
parser.add_argument('--preset', choices=('D', 'E'), required=True)
parser.add_argument('--glslang', type=Path, required=True)
parser.add_argument('--bash', type=Path, required=True)
args = parser.parse_args()
repo = Path(__file__).resolve().parents[2]
model = args.model.resolve()
if not model.is_relative_to((repo / 'build').resolve()):
parser.error('model cache must stay under ignored build/')
import numpy
sources = {}
for directory in ('engine', 'vulkan', 'kernels/tools', 'scripts/windows'):
for path in sorted((repo / directory).rglob('*')):
if path.is_file() and path.suffix in ('.py', '.ps1', '.sh', '.comp', '.glsl', '.h', '.inc', '.txt'):
sources[path.relative_to(repo).as_posix()] = sha(path)
identity = dict(schema=1, preset=args.preset, architecture='gfx1101', arithmetic='exact-gfx11',
numpyVersion=numpy.__version__, sources=sources,
tools={name: sha(path) for name, path in (
('dll', args.dll), ('glslang', args.glslang),
('bash', args.bash), ('python', Path(sys.executable)))})
stamp = model / 'windows-rr-cache.json'
products = {path.name: sha(path) for path in sorted(model.iterdir())
if path.is_file() and path.name not in (stamp.name, stamp.name + '.tmp')}
for name in ('rr.txt', 'model.json', 'enc0_m.spv', 'dec0_m.spv', 'hkpn.spv', 'enc0.args', 'hkpn.args'):
if name not in products:
raise RuntimeError(f'incomplete model: {name}')
manifest = (model / 'rr.txt').read_text().splitlines()
if (not manifest or manifest[0] != 'D4RR0001' or
f'preset {4 if args.preset == "D" else 5}' not in manifest or
'arithmetic gfx11' not in manifest):
raise RuntimeError('RR manifest preset/arithmetic does not match requested cache identity')
stages = [line.split() for line in manifest if line.startswith('stage ')]
expected_stages = {'enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5',
'dec4', 'dec3', 'dec2', 'dec1', 'dec0', 'hkpn'}
if len(stages) != 12 or {fields[1] for fields in stages} != expected_stages:
raise RuntimeError('RR stage list is incomplete or duplicated')
for fields in stages:
for name in (fields[2], fields[4]):
if name != '-' and name not in products:
raise RuntimeError(f'RR stage artifact missing or outside model directory: {name}')
if args.action == 'check':
cached = json.loads(stamp.read_text())
if cached.get('identity') != identity:
raise RuntimeError('RR cache identity changed; use a new output directory to rebuild')
if cached.get('products') != products:
raise RuntimeError('RR cache artifacts changed; use a new output directory to rebuild')
print(f'RR_CACHE_HIT preset={args.preset} artifacts={len(products)} model={model}')
else:
temporary = stamp.with_name(stamp.name + '.tmp')
temporary.write_text(json.dumps(dict(identity=identity, products=products), indent=2) + '\n')
temporary.replace(stamp)
print(f'RR_CACHE_WRITTEN preset={args.preset} artifacts={len(products)} model={model}')
if __name__ == '__main__':
main()
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Generate deterministic RR replay inputs. This is a dispatch smoke, not a quality reference."""
import argparse
import json
import math
from pathlib import Path
import struct
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('output', type=Path)
parser.add_argument('--width', type=int, default=64)
parser.add_argument('--height', type=int, default=64)
parser.add_argument('--frames', type=int, default=3)
parser.add_argument('--output-width', type=int)
parser.add_argument('--output-height', type=int)
args = parser.parse_args()
if not (32 <= args.width <= 1024 and 32 <= args.height <= 1024 and 1 <= args.frames <= 32):
parser.error('width/height must be 32..1024; frames must be 1..32')
if bool(args.output_width) != bool(args.output_height):
parser.error('supply both output dimensions')
ow, oh = args.output_width or args.width * 2, args.output_height or args.height * 2
if not (1 <= ow <= 2560 and 1 <= oh <= 1440):
parser.error('output must fit 2560x1440')
args.output.mkdir(parents=True, exist_ok=False)
identity = '1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1'
lines = [f'size {args.width} {args.height} {ow} {oh}']
for frame in range(args.frames):
data = {name: bytearray() for name in ('color', 'motion', 'depth', 'normals', 'roughness', 'diffuse', 'specular')}
for y in range(args.height):
for x in range(args.width):
u, v = x / (args.width - 1), y / (args.height - 1)
noise = math.sin(x * 1.7 + y * 2.3 + frame) * 0.025
data['color'] += struct.pack('<4e', 0.1 + u + noise, 0.1 + v + noise, 0.2 + u * v, 1)
data['motion'] += struct.pack('<2e', 0, 0)
data['depth'] += struct.pack('<f', 1 + v)
data['normals'] += struct.pack('<4e', 0, 0, 1, 1)
data['roughness'] += struct.pack('<4e', 0.5, 0.5, 0.5, 0.5)
data['diffuse'] += bytes((128, 160, 192, 255))
data['specular'] += bytes((10, 10, 10, 255))
for name, blob in data.items():
(args.output / f'{frame}-{name}.bin').write_bytes(blob)
lines.append(f'frame {frame} 0 0 1 1 1 {int(frame == 0)} 0 {identity} {identity}')
(args.output / 'frames.txt').write_text('\n'.join(lines) + '\n', encoding='ascii')
(args.output / 'synthetic.json').write_text(json.dumps({
'kind': 'synthetic-dispatch-only', 'render': [args.width, args.height],
'output': [ow, oh], 'frames': args.frames,
'quality_reference': False,
}, indent=2) + '\n', encoding='ascii')
if __name__ == '__main__':
main()
+27
View File
@@ -0,0 +1,27 @@
[CmdletBinding()]
param()
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
$directory=Join-Path $repo '.tools/fsr-rr-1.2'
$dll=Join-Path $directory 'amd_fidelityfx_denoiser_dx12.dll'
$expected='48f1e5888ba6a0a3d59a98b9751e37c392b0f7b8c223d0082d5c1f40642879d3'
$url='https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v2.3.0/Kits/FidelityFX/signedbin/amd_fidelityfx_denoiser_dx12.dll'
New-Item -ItemType Directory -Path $directory -Force | Out-Null
if(Test-Path -LiteralPath $dll){
if((Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected){
throw "Existing denoiser hash mismatch; refusing to replace $dll"
}
}else{
$temporary=Join-Path $directory ([Guid]::NewGuid().ToString('N')+'.download')
try{
Invoke-WebRequest -Uri $url -OutFile $temporary -UseBasicParsing
if((Get-FileHash -LiteralPath $temporary -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected){
throw 'AMD denoiser download hash mismatch.'
}
Move-Item -LiteralPath $temporary -Destination $dll
}finally{
if(Test-Path -LiteralPath $temporary){Remove-Item -LiteralPath $temporary}
}
}
Write-Host "Pinned AMD denoiser ready: $dll"
Write-Host 'Provider availability and execution still require test-fsr-rr.ps1.'
+20 -3
View File
@@ -57,7 +57,7 @@ function Expect([string]$case,$run,[int]$exit,[string[]]$patterns) {
$game=New-Install 'healthy'; $inv=Write-Inventory 'a' 'gfx1101'
Expect 'healthy-install' (Run-Check $game $inv) 0 @('ok\s+d4r\\dlss\\nvngx_dlss\.dll \(nvngx_dlss\.dll 310\.9\.1, tested on Windows\)','ok\s+d4r\\ngx\\_nvngx\.dll',
'ok\s+GPU AMD Radeon RX 7700 XT \(gfx1101\): build present, 16 native kernels','ok\s+all 17 GPU code objects are built for gfx1101',
'ok\s+no vkd3d/Wine d3d12\.dll','ok\s+d4r\.ini is valid \(model K\)','compile-tested','Everything d4r needs is in place')
'ok\s+no vkd3d/Wine d3d12\.dll','ok\s+d4r\.ini is valid \(model K\)','RX 7700 XT has passed native Windows synthetic checks','Everything d4r needs is in place')
# 2. NVIDIA DLL problems, each with the fix.
$game=New-Install 'dll-missing'; Remove-Item -LiteralPath (Join-Path $game 'd4r/dlss/nvngx_dlss.dll')
@@ -78,8 +78,8 @@ $game=New-Install 'opti-path'; (Get-Content -LiteralPath (Join-Path $game 'OptiS
Expect 'optiscaler-ini-path' (Run-Check $game $inv) 1 @('MISSING\s+OptiScaler\.ini NvngxPath = nowhere does not hold _nvngx\.dll')
# 4. d4r.ini mistakes are notes with what will happen instead.
$game=New-Install 'ini'; Set-Content -LiteralPath (Join-Path $game 'd4r/d4r.ini') "[DLSS]`nModel = E`nShowWatermark = sometimes`n[Latency]`nFrameAge = 1`n[Debug]`nLog = loud`n" -Encoding ASCII
Expect 'ini-mistakes' (Run-Check $game $inv) 0 @("note\s+d4r\.ini: \[DLSS\] Model 'E' is not available on Windows",'must be true or false, not ''sometimes''','\[Latency\] FrameAge is not a Windows setting','Log must be normal or verbose')
$game=New-Install 'ini'; Set-Content -LiteralPath (Join-Path $game 'd4r/d4r.ini') "[DLSS]`nModel = Z`nShowWatermark = sometimes`n[Latency]`nFrameAge = 1`n[Debug]`nLog = loud`n" -Encoding ASCII
Expect 'ini-mistakes' (Run-Check $game $inv) 0 @("note\s+d4r\.ini: \[DLSS\] Model 'Z' is not available on Windows",'must be true or false, not ''sometimes''','\[Latency\] FrameAge is not a Windows setting','Log must be normal or verbose')
# 5. GPU cases.
$game=New-Install 'gpu-no-build'; Remove-Item -LiteralPath (Join-Path $game 'd4r/_nvngx_gfx1103.dll')
@@ -119,5 +119,22 @@ $exitEmpty=$LASTEXITCODE; $ErrorActionPreference='Stop'
if ($exitEmpty -ne 1 -or $text -notmatch 'no d4r folder here') { throw "A folder without d4r must be reported.`n$text" }
$results+=@{case='no-d4r-folder'; passed=$true}
foreach($preset in @('E','L')){
$game=New-Install "ini-$preset"
Set-Content -LiteralPath (Join-Path $game 'd4r/d4r.ini') "[DLSS]`nModel=$preset`n" -Encoding ASCII
Expect "ini-$preset" (Run-Check $game $inv) 0 @("d4r.ini is valid \(model $preset\)")
}
$game=New-Install 'private-native'
$private=Join-Path $game 'd4r/private-native'
New-Item -ItemType Directory -Path $private | Out-Null
Copy-Item (Join-Path $game 'd4r/native/gfx1101/*') $private
Set-Content -LiteralPath (Join-Path $game 'd4r/d4r.ini') "[Paths]`nNativeDir=private-native`n" -Encoding ASCII
Expect 'private-native-valid' (Run-Check $game $inv) 0 @('ok\s+private native kernel directory')
$object=Get-ChildItem -LiteralPath $private -Filter '*.hsaco' | Select-Object -First 1
$header=[IO.File]::ReadAllBytes($object.FullName);$header[48]=0;[IO.File]::WriteAllBytes($object.FullName,$header)
Expect 'private-native-target-rejected' (Run-Check $game $inv) 1 @('NativeDir .* is not built for gfx1101')
Remove-Item -LiteralPath $object.FullName
Expect 'private-native-incomplete-rejected' (Run-Check $game $inv) 1 @('NativeDir is incomplete')
$results | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
Write-Host "PASS D4R_CHECK cases=$($results.Count) Report: $OutputDirectory"
+45
View File
@@ -0,0 +1,45 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$Probe,
[Parameter(Mandatory=$true)][string]$DenoiserDll,
[Parameter(Mandatory=$true)][string]$OutputDirectory,
[switch]$QueryOnly,
[ValidateRange(1,600)][int]$TimeoutSeconds=120
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
$Probe=(Get-Item -LiteralPath $Probe).FullName
$DenoiserDll=(Get-Item -LiteralPath $DenoiserDll).FullName
$expected='48f1e5888ba6a0a3d59a98b9751e37c392b0f7b8c223d0082d5c1f40642879d3'
if((Get-FileHash -LiteralPath $DenoiserDll -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected){
throw 'Expected unmodified AMD SDK 2.3 denoiser 1.2.0.2740.'
}
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
$buildRoot=[IO.Path]::GetFullPath((Join-Path $repo 'build'))
if(!$OutputDirectory.StartsWith($buildRoot+'\',[StringComparison]::OrdinalIgnoreCase)){
throw 'Probe output must stay under the ignored build directory.'
}
if(Test-Path -LiteralPath $OutputDirectory){throw 'Use a new probe output directory.'}
New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
# Start-Process joins arguments into one command line. Reject embedded quotes;
# quote absolute file paths so spaces in the workspace are supported.
foreach($path in @($DenoiserDll,$OutputDirectory)){
if($path.Contains('"')){throw 'Embedded quotes are not supported in probe paths.'}
}
$arguments='"'+$DenoiserDll+'" "'+$OutputDirectory+'" --size 64x64 --frames 48'
if($QueryOnly){$arguments+=' --query-only'}
$process=Start-Process -FilePath $Probe -ArgumentList $arguments -WindowStyle Hidden -PassThru `
-RedirectStandardOutput (Join-Path $OutputDirectory 'stdout.log') `
-RedirectStandardError (Join-Path $OutputDirectory 'stderr.log')
try{
$deadline=[DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
while(!$process.WaitForExit(1000)){
if([DateTime]::UtcNow -ge $deadline){
Stop-Process -Id $process.Id -ErrorAction SilentlyContinue
throw "FSR RR probe timed out after $TimeoutSeconds seconds; exact probe process stopped."
}
}
$process.WaitForExit()
Get-Content -LiteralPath (Join-Path $OutputDirectory 'stdout.log')
if($process.ExitCode -ne 0){throw "FSR RR probe failed ($($process.ExitCode)); see $OutputDirectory"}
}finally{$process.Dispose()}
+53
View File
@@ -0,0 +1,53 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$Probe,
[Parameter(Mandatory=$true)][string]$ModelDirectory,
[Parameter(Mandatory=$true)][string]$InputDirectory,
[Parameter(Mandatory=$true)][string]$ReferenceDirectory,
[Parameter(Mandatory=$true)][string]$OutputDirectory
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
foreach($name in @('Probe','ModelDirectory','InputDirectory','ReferenceDirectory')){
Set-Variable -Name $name -Value ((Get-Item -LiteralPath (Get-Variable -Name $name -ValueOnly)).FullName)
}
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
if(!$OutputDirectory.StartsWith([IO.Path]::GetFullPath((Join-Path $repo 'build'))+'\',[StringComparison]::OrdinalIgnoreCase)){
throw 'RR bridge outputs must stay under ignored build/.'
}
if(Test-Path -LiteralPath $OutputDirectory){throw 'Use a fresh RR bridge output directory.'}
New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
$frames=@(Get-Content -LiteralPath (Join-Path $InputDirectory 'frames.txt') | Where-Object {$_ -match '^frame '}).Count
if(!$frames -or $frames -gt 32){throw 'Need 1..32 RR fixture frames.'}
$arguments=@('--rr-model',$ModelDirectory,'--rr-inputs',$InputDirectory,'--rr-reference',$ReferenceDirectory,
'--rr-output',(Join-Path $OutputDirectory 'frames'))
foreach($value in $arguments){if($value.Contains('"')){throw 'Embedded quotes are not supported in probe paths.'}}
$quoted=($arguments | ForEach-Object {'"'+$_+'"'}) -join ' '
$process=Start-Process -FilePath $Probe -ArgumentList $quoted -WindowStyle Hidden -PassThru `
-RedirectStandardOutput (Join-Path $OutputDirectory 'stdout.log') `
-RedirectStandardError (Join-Path $OutputDirectory 'stderr.log')
try{
$deadline=[DateTime]::UtcNow.AddSeconds(180)
while(!$process.WaitForExit(1000)){
if([DateTime]::UtcNow -ge $deadline){Stop-Process -Id $process.Id;throw 'Exact RR bridge process stopped at 180-second limit.'}
}
$process.WaitForExit()
$text=Get-Content -LiteralPath (Join-Path $OutputDirectory 'stdout.log') -Raw
Get-Content -LiteralPath (Join-Path $OutputDirectory 'stdout.log')
$records=[regex]::Matches($text,'(?m)^RR_BRIDGED_FRAME .*finite=1 nonuniform=1 standalone_exact=1 ')
if($process.ExitCode -ne 0 -or $records.Count -ne $frames -or $text -notmatch 'PASS RR_BRIDGED_INFERENCE'){
Get-Content -LiteralPath (Join-Path $OutputDirectory 'stderr.log')
throw "RR bridged inference failed ($($process.ExitCode)); see $OutputDirectory"
}
$outputs=@(Get-ChildItem -LiteralPath (Join-Path $OutputDirectory 'frames') -Filter '*-out.bin')
if($outputs.Count -ne $frames){throw 'Incomplete RR output file set.'}
foreach($output in $outputs){
if((Get-FileHash -LiteralPath $output.FullName).Hash -ne (Get-FileHash -LiteralPath (Join-Path $ReferenceDirectory $output.Name)).Hash){
throw 'Independent bridged/reference output hash comparison failed.'
}
}
@{passed=$true;frames=$frames;guides=7;standaloneExact=$true;gpuFenceHandoff=$true;
gameAccessed=$false;qualityReference=$false;d3d12Textures=$true;probeSha256=(Get-FileHash -LiteralPath $Probe).Hash;
model=$ModelDirectory;inputs=$InputDirectory;reference=$ReferenceDirectory} |
ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
}finally{$process.Dispose()}
+47
View File
@@ -0,0 +1,47 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$Probe,
[Parameter(Mandatory=$true)][string]$OutputDirectory
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
$Probe=(Get-Item -LiteralPath $Probe).FullName
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
if(!$OutputDirectory.StartsWith([IO.Path]::GetFullPath((Join-Path $repo 'build'))+'\',[StringComparison]::OrdinalIgnoreCase)){
throw 'RR interop probe reports must stay under ignored build/.'
}
if(Test-Path -LiteralPath $OutputDirectory){throw 'Use a new output directory.'}
New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
$cases=@(
@{name='smoke';bytes=1048576;iterations=8},
@{name='render-colour-1712x960-rgba16f-padded';bytes=13271040;iterations=3;width=1712;height=960},
@{name='output-colour-2560x1440-rgba16f';bytes=29491200;iterations=3;width=2560;height=1440}
)
$results=@()
foreach($case in $cases){
$stdout=Join-Path $OutputDirectory "$($case.name).stdout.log"
$stderr=Join-Path $OutputDirectory "$($case.name).stderr.log"
$arguments=@('--bytes',"$($case.bytes)",'--iterations',"$($case.iterations)")
if($case.width){$arguments+=@('--image-width',"$($case.width)",'--image-height',"$($case.height)")}
$process=Start-Process -FilePath $Probe -ArgumentList $arguments `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $stdout -RedirectStandardError $stderr
try{
if(!$process.WaitForExit(30000)){
Stop-Process -Id $process.Id -ErrorAction SilentlyContinue
throw "Exact RR interop probe stopped after 30 seconds: $($case.name)"
}
$process.WaitForExit()
$text=Get-Content -LiteralPath $stdout -Raw
$records=[regex]::Matches($text,'(?m)^RR_INTEROP_ITERATION .* exact=1 ')
if($process.ExitCode -ne 0 -or $records.Count -ne $case.iterations -or
$text -notmatch 'PASS RR_INTEROP_TRANSFER .*gpu_fence_handoff=1 .*inference_executed=0'){
throw "RR interop case failed ($($process.ExitCode)): $($case.name); see $OutputDirectory"
}
if($case.width -and $text -notmatch 'canonical_image=1'){throw 'Canonical image path was not exercised.'}
$results+=@{name=$case.name;bytes=$case.bytes;iterations=$case.iterations;exactBothDirections=$true;canonicalImage=[bool]$case.width}
Write-Host "PASS RR_INTEROP $($case.name): $($case.iterations) iterations, $($case.bytes) bytes"
}finally{$process.Dispose()}
}
@{passed=$true;probeSha256=(Get-FileHash -LiteralPath $Probe).Hash;cases=$results;
gpuFenceHandoff=$true;inferenceExecuted=$false;gameAccessed=$false} |
ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
@@ -107,5 +107,36 @@ try { Invoke-Package $prefix (Join-Path $OutputDirectory 'release-runtime') } ca
if (!$rejected) { throw 'A differing shared runtime was not rejected.' }
$results+=@{case='shared-runtime-mismatch-rejected'; passed=$true}
# Local ZIPs may include uncommitted inputs, but remain under ignored build/ and
# preserve all target/hash/runtime checks. This fixture is never a usable game ZIP.
$prefix=Join-Path $OutputDirectory 'inputs/development/game'
New-Inputs $prefix
foreach($target in $targets){
$metadataPath="$prefix-$($target.architecture)/package.json"
$metadata=Get-Content -LiteralPath $metadataPath -Raw | ConvertFrom-Json
$metadata.d4rWorkingTreeDirty=$true
$metadata | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $metadataPath -Encoding UTF8
}
$developmentRoot=Join-Path $repo ('build/release-development-fixture-'+[Guid]::NewGuid().ToString('N'))
$zip=Join-Path $developmentRoot 'fixture.zip'
& (Join-Path $PSScriptRoot 'package-windows-release.ps1') -GamePackagePrefix $prefix -LoaderDll $loader `
-PackageRoot (Join-Path $developmentRoot 'package') -ArchivePath $zip -LocalDevelopment | Out-Null
$metadata=Get-Content -LiteralPath (Join-Path $developmentRoot 'package/d4r/package.json') -Raw | ConvertFrom-Json
if(!$metadata.localDevelopment){throw 'Local ZIP lacks development marker.'}
$archive=[IO.Compression.ZipFile]::OpenRead($zip)
try{
foreach($required in @('install.bat','install-dlss.ps1','d4r/d4r.ini')){
if(!$archive.GetEntry($required)){throw "Local ZIP lacks $required"}
}
}finally{$archive.Dispose()}
$results+=@{case='local-development-zip'; passed=$true}
$rejected=$false
try{
& (Join-Path $PSScriptRoot 'package-windows-release.ps1') -GamePackagePrefix $prefix -LoaderDll $loader `
-PackageRoot (Join-Path $repo 'dist/rejected-local-fixture') -LocalDevelopment | Out-Null
}catch{$rejected=$_.Exception.Message -match 'must stay under ignored build/'}
if(!$rejected){throw 'Local package escaped build/.'}
$results+=@{case='local-development-path-rejected'; passed=$true}
$results | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
Write-Host "PASS WINDOWS_RELEASE_PACKAGE cases=$($results.Count) Report: $OutputDirectory"
+53
View File
@@ -0,0 +1,53 @@
import math
from pathlib import Path
import random
import re
import struct
import sys
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'tools/windows'))
from llvm22_ir import compatible_ir, float_hex
class CompatibilityTests(unittest.TestCase):
def test_float_widening_preserves_finite_values(self):
rng = random.Random(22)
patterns = [rng.getrandbits(32) for _ in range(20000)]
patterns += [(sign << 31) | (exponent << 23) | mantissa
for sign in (0, 1) for exponent in range(255)
for mantissa in (0, 1, 0x3fffff, 0x400000, 0x7fffff)]
for bits in patterns:
value = struct.unpack('>f', bits.to_bytes(4, 'big'))[0]
if not math.isfinite(value):
continue
rendered = float_hex(re.match(r'(.*)', f'{bits:08X}'))
self.assertEqual(bytes.fromhex(rendered[2:]), struct.pack('>d', value))
def test_nan_payload_and_signaling_bit_preserved(self):
for source, expected in [('7F800001', '0x7FF0000020000000'),
('FFC00001', '0xFFF8000020000000')]:
self.assertEqual(float_hex(re.match(r'(.*)', source)), expected)
def test_decimal_half_and_float(self):
result = compatible_ir('%a = fcmp ult float %x, 8.507060e+37\n'
'%b = fmul half %h, 1.953130e-03\n')
self.assertIn('0x47D0000020000000', result)
self.assertIn('0xH1800', result)
def test_double_and_strings_not_rounded_to_float(self):
text = '%a = fmul double %x, 1.23456789012345e+00\n!0 = !{!"float 1.234567e+00"}\n'
self.assertEqual(compatible_ir(text), text)
def test_denormal_mode_and_conservative_promises(self):
result = compatible_ir('attributes #0 = { denormal_fpenv(dynamic) '
'nocreateundeforpoison memory(read, target_mem: none) }\n'
'%x = uitofp contract nneg i32 %v to double\n')
self.assertIn('"denormal-fp-math"="dynamic,dynamic"', result)
self.assertNotIn('nocreateundeforpoison', result)
self.assertNotIn('target_mem', result)
self.assertIn('uitofp nneg i32', result)
if __name__ == '__main__':
unittest.main()
+34
View File
@@ -0,0 +1,34 @@
import sys
from pathlib import Path
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'tools/windows'))
from deploy_rr_model import select_native
class ConfigSelectionTests(unittest.TestCase):
def test_comments_bom_and_crlf_survive(self):
source = b'\xef\xbb\xbf[DLSS]\r\nModel = K ; keep SR\r\n[RayReconstruction]\r\nBackend = off ; keep comment\r\nModelDir = auto # keep path note\r\n'
result = select_native(source, 'engine/rr-d-test')
self.assertEqual(result, source.replace(b'Backend = off', b'Backend = native').replace(b'ModelDir = auto', b'ModelDir = engine/rr-d-test'))
self.assertEqual(select_native(result, 'engine/rr-d-test'), result)
def test_missing_section_preserves_other_settings(self):
source = b'[DLSS]\nModel=M\n; custom note'
result = select_native(source, 'engine/rr-e-test')
self.assertTrue(result.startswith(source))
self.assertIn(b'[RayReconstruction]\nBackend = native\nModelDir = engine/rr-e-test\n', result)
self.assertEqual(select_native(result, 'engine/rr-e-test'), result)
def test_duplicate_sections_cannot_restore_old_selection(self):
source = b'[RayReconstruction]\nBackend=off\n[Other]\nKeep=yes\n[rayreconstruction]\nModelDir=old-model\n'
result = select_native(source, 'engine/rr-d-test')
self.assertNotIn(b'old-model', result)
self.assertNotIn(b'Backend=off', result)
self.assertEqual(result.count(b'engine/rr-d-test'), 2)
self.assertIn(b'[Other]\nKeep=yes\n', result)
self.assertEqual(select_native(result, 'engine/rr-d-test'), result)
if __name__ == '__main__':
unittest.main()
+20
View File
@@ -0,0 +1,20 @@
These headers are copied unmodified from the AMD FidelityFX SDK
(https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK), commit
60f4ea81909200d8542eca14dccb2628b763a9a3 (SDK 2.3.0, FSR Ray Regeneration 1.2.0):
api/include/ffx_api.h
api/include/ffx_api_types.h
denoisers/include/ffx_denoiser.h
The SDK license (Kits/FidelityFX/docs/license.md) lists these files as exceptions
that are subject to the following MIT license. The AMD runtime DLLs
(amd_fidelityfx_*_dx12.dll) are NOT part of this repository and are governed by
AMD's binary-only terms.
Copyright (C) Advanced Micro Devices, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+213
View File
@@ -0,0 +1,213 @@
// This file is part of the FidelityFX SDK.
//
// Copyright (C) 2026 Advanced Micro Devices, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#pragma once
#if defined(__cplusplus)
extern "C" {
#endif // #if defined(__cplusplus)
#define FFX_API_ENTRY __declspec(dllexport)
#include <stdint.h>
enum FfxApiReturnCodes
{
FFX_API_RETURN_OK = 0, ///< The oparation was successful.
FFX_API_RETURN_ERROR = 1, ///< An error occurred that is not further specified.
FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE = 2, ///< The structure type given was not recognized for the function or context with which it was used. This is likely a programming error.
FFX_API_RETURN_ERROR_RUNTIME_ERROR = 3, ///< The underlying runtime (e.g. D3D12, Vulkan) or effect returned an error code.
FFX_API_RETURN_NO_PROVIDER = 4, ///< No provider was found for the given structure type. This is likely a programming error.
FFX_API_RETURN_ERROR_MEMORY = 5, ///< A memory allocation failed.
FFX_API_RETURN_ERROR_PARAMETER = 6, ///< A parameter was invalid, e.g. a null pointer, empty resource or out-of-bounds enum value.
FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE = 7, ///< The structure type given is new and not supported in the old provider. This is likely fixed with driver upgrade or effect DLL upgrade.
};
typedef void* ffxContext;
typedef uint32_t ffxReturnCode_t;
#define FFX_API_EFFECT_MASK 0x00ff0000u
#define FFX_API_BACKEND_MASK 0xff000000u
#define FFX_API_EFFECT_ID_GENERAL 0x00000000u
// Base Descriptor types
typedef uint64_t ffxStructType_t;
typedef struct ffxApiHeader
{
ffxStructType_t type; ///< The structure type. Must always be set to the corresponding value for any structure (found nearby with a similar name).
struct ffxApiHeader* pNext; ///< Pointer to next structure, used for optional parameters and extensions. Can be null.
} ffxApiHeader;
typedef ffxApiHeader ffxCreateContextDescHeader;
typedef ffxApiHeader ffxConfigureDescHeader;
typedef ffxApiHeader ffxQueryDescHeader;
typedef ffxApiHeader ffxDispatchDescHeader;
// Extensions for global debug
#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_SILENCE 0x0000000u
#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_ERRORS 0x0000001u
#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_WARNINGS 0x0000002u
#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE 0xfffffffu
enum FfxApiMsgType
{
FFX_API_MESSAGE_TYPE_ERROR = 0,
FFX_API_MESSAGE_TYPE_WARNING = 1,
FFX_API_MESSAGE_TYPE_COUNT
};
typedef void (*ffxApiMessage)(uint32_t type, const wchar_t* message);
#define FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1 0x0000001u
struct ffxConfigureDescGlobalDebug1
{
ffxConfigureDescHeader header;
ffxApiMessage fpMessage;
uint32_t debugLevel;
};
#define FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG 7u
struct ffxConfigureDescGlobalDebug
{
ffxConfigureDescHeader header;
uint64_t effectId;
ffxApiMessage fpMessage; ///< A pointer to a function that can receive messages from the runtime. May be null.
uint32_t debugLevel;
};
#define FFX_API_QUERY_DESC_TYPE_GET_VERSIONS 4u
struct ffxQueryDescGetVersions
{
ffxQueryDescHeader header;
uint64_t createDescType; ///< Create description for the effect whose versions should be enumerated.
void* device; ///< For DX12: pointer to ID3D12Device.
uint64_t *outputCount; ///< Input capacity of id and name arrays. Output number of returned versions. If initially zero, output is number of available versions.
uint64_t *versionIds; ///< Output array of version ids to be used as version overrides. If null, only names and count are returned.
const char** versionNames; ///< Output array of version names for display. If null, only ids and count are returned. If both this and versionIds are null, only count is returned.
};
#define FFX_API_DESC_TYPE_OVERRIDE_VERSION 5u
struct ffxOverrideVersion
{
ffxApiHeader header;
uint64_t versionId; ///< Id of version to use. Must be a value returned from a query in ffxQueryDescGetVersions.versionIds array.
};
#define FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION 6u
struct ffxQueryGetProviderVersion
{
ffxQueryDescHeader header;
uint64_t versionId; ///< Id of provider being used for queried context. 0 if invalid.
const char* versionName; ///< Version name for display. If nullptr, the query was invalid.
};
// Memory allocation function. Must return a valid pointer to at least size bytes of memory aligned to hold any type.
// May return null to indicate failure. Standard library malloc fulfills this requirement.
typedef void* (*ffxAlloc)(void* pUserData, uint64_t size);
// Memory deallocation function. May be called with null pointer as second argument.
typedef void (*ffxDealloc)(void* pUserData, void* pMem);
typedef struct ffxAllocationCallbacks
{
void* pUserData;
ffxAlloc alloc;
ffxDealloc dealloc;
} ffxAllocationCallbacks;
// Creates a FFX object context.
// Depending on the desc structures provided to this function, the context will be created with the desired version and attributes.
// Non-zero return indicates error code.
// Pointers passed in desc must remain live until ffxDestroyContext is called on the context.
// MemCb may be null; the system allocator (malloc/free) will be used in this case.
FFX_API_ENTRY ffxReturnCode_t ffxCreateContext(ffxContext* context, ffxCreateContextDescHeader* desc, const ffxAllocationCallbacks* memCb);
typedef ffxReturnCode_t (*PfnFfxCreateContext)(ffxContext* context, ffxCreateContextDescHeader* desc, const ffxAllocationCallbacks* memCb);
// Destroys an FFX object context.
// Non-zero return indicates error code.
// MemCb must be compatible with the callbacks passed into ffxCreateContext.
FFX_API_ENTRY ffxReturnCode_t ffxDestroyContext(ffxContext* context, const ffxAllocationCallbacks* memCb);
typedef ffxReturnCode_t (*PfnFfxDestroyContext)(ffxContext* context, const ffxAllocationCallbacks* memCb);
// Configures the provided FFX object context.
// If context is null, configure operates on any global state.
// Non-zero return indicates error code.
FFX_API_ENTRY ffxReturnCode_t ffxConfigure(ffxContext* context, const ffxConfigureDescHeader* desc);
typedef ffxReturnCode_t (*PfnFfxConfigure)(ffxContext* context, const ffxConfigureDescHeader* desc);
// Queries the provided FFX object context.
// If context is null, query operates on any global state.
// Non-zero return indicates error code.
FFX_API_ENTRY ffxReturnCode_t ffxQuery(ffxContext* context, ffxQueryDescHeader* desc);
typedef ffxReturnCode_t (*PfnFfxQuery)(ffxContext* context, ffxQueryDescHeader* desc);
// Dispatches work on the given FFX object context defined by the dispatch descriptor.
// Non-zero return indicates error code.
FFX_API_ENTRY ffxReturnCode_t ffxDispatch(ffxContext* context, const ffxDispatchDescHeader* desc);
typedef ffxReturnCode_t (*PfnFfxDispatch)(ffxContext* context, const ffxDispatchDescHeader* desc);
// FFX_API_EFFECT_IDs
#define FFX_API_EFFECT_ID_UPSCALE 0x00010000u
#define FFX_API_EFFECT_ID_FRAMEGENERATION 0x00020000u
#define FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN 0x00030000u
// Need to keep this ID around for the deprecated VK frame gen swapchain
#define FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK 0x00040000u
// Need to keep this ID around for the deprecated VK frame gen swapchain
#define FFX_API_EFFECT_ID_DENOISER 0x00050000u
#define FFX_API_EFFECT_ID_RADIANCECACHE 0x00060000u
#define FFX_API_MAKE_EFFECT_SUB_ID(effectId, subversion) ((effectId & FFX_API_EFFECT_MASK) | (subversion & ~FFX_API_EFFECT_MASK))
// FFX_APID_BACKEND_IDs
#define FFX_API_BACKEND_ID_DX12 0x00000000u
#define FFX_API_BACKEND_ID_XBOX 0x01000000u
#define FFX_API_BACKEND_ID_VK 0x02000000u // For new effects going forward, please use this backend ID for vulkan specifics
#define FFX_API_MAKE_BACKEND_SUB_ID(backendId, subversion) ((backendId & FFX_API_BACKEND_MASK) | (subversion & ~FFX_API_BACKEND_MASK))
// Combiner for BACKEND-specific EFFECT sub-Ids
#define FFX_API_MAKE_BACKEND_EFFECT_SUB_ID(backendId, effectId, subversion) ((subversion & ~FFX_API_EFFECT_MASK) | (effectId & FFX_API_EFFECT_MASK) | (backendId & FFX_API_BACKEND_MASK) | (subversion & ~(FFX_API_BACKEND_MASK | FFX_API_EFFECT_MASK)))
// Pragma macros for controlling warnings so that deprecations take affect externally but can be suppressed internally.
// This is so we can maintain the API/ABI until we are ready to make the breaking change.
// These are sadly compiler specific.
#if defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__) && !defined(__INTEL_COMPILER)
#define FFX_PRAGMA_WARNING_PUSH warning( push )
#define FFX_PRAGMA_WARNING_POP warning( pop )
#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS warning( disable: 4996 )
#define FFX_PRAGMA_WARNING_WARN_DEPRECATIONS warning( default: 4996 )
#elif defined(__clang__) || defined(__GNUC__)
#define FFX_PRAGMA_WARNING_PUSH GCC diagnostic push
#define FFX_PRAGMA_WARNING_POP GCC diagnostic pop
#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS GCC diagnostic ignored "-Wdeprecated"
#define FFX_PRAGMA_WARNING_WARN_DEPRECATIONS GCC diagnostic warning "-Wdeprecated"
#else
#define FFX_PRAGMA_WARNING_PUSH
#define FFX_PRAGMA_WARNING_POP
#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS
#endif
#define FFX_DEPRECATION(message) [[deprecated(message)]]
#if defined(__cplusplus)
}
#endif // #if defined(__cplusplus)
+257
View File
@@ -0,0 +1,257 @@
// This file is part of the FidelityFX SDK.
//
// Copyright (C) 2026 Advanced Micro Devices, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#pragma once
#include <stdint.h>
#include <stdbool.h>
/// An enumeration of surface formats. Needs to match enum FfxApiSurfaceFormat
enum FfxApiSurfaceFormat
{
FFX_API_SURFACE_FORMAT_UNKNOWN, ///< Unknown format
FFX_API_SURFACE_FORMAT_R32G32B32A32_TYPELESS, ///< 32 bit per channel, 4 channel typeless format
FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT, ///< 32 bit per channel, 4 channel uint format
FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT, ///< 32 bit per channel, 4 channel float format
FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, ///< 16 bit per channel, 4 channel float format
FFX_API_SURFACE_FORMAT_R32G32B32_FLOAT, ///< 32 bit per channel, 3 channel float format
FFX_API_SURFACE_FORMAT_R32G32_FLOAT, ///< 32 bit per channel, 2 channel float format
FFX_API_SURFACE_FORMAT_R8_UINT, ///< 8 bit per channel, 1 channel float format
FFX_API_SURFACE_FORMAT_R32_UINT, ///< 32 bit per channel, 1 channel float format
FFX_API_SURFACE_FORMAT_R8G8B8A8_TYPELESS, ///< 8 bit per channel, 4 channel typeless format
FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, ///< 8 bit per channel, 4 channel unsigned normalized format
FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM, ///< 8 bit per channel, 4 channel signed normalized format
FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB, ///< 8 bit per channel, 4 channel srgb normalized
FFX_API_SURFACE_FORMAT_B8G8R8A8_TYPELESS, ///< 8 bit per channel, 4 channel typeless format
FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM, ///< 8 bit per channel, 4 channel unsigned normalized format
FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB, ///< 8 bit per channel, 4 channel srgb normalized
FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT, ///< 32 bit 3 channel float format
FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, ///< 10 bit per 3 channel, 2 bit for 1 channel normalized format
FFX_API_SURFACE_FORMAT_R16G16_FLOAT, ///< 16 bit per channel, 2 channel float format
FFX_API_SURFACE_FORMAT_R16G16_UINT, ///< 16 bit per channel, 2 channel unsigned int format
FFX_API_SURFACE_FORMAT_R16G16_SINT, ///< 16 bit per channel, 2 channel signed int format
FFX_API_SURFACE_FORMAT_R16_FLOAT, ///< 16 bit per channel, 1 channel float format
FFX_API_SURFACE_FORMAT_R16_UINT, ///< 16 bit per channel, 1 channel unsigned int format
FFX_API_SURFACE_FORMAT_R16_UNORM, ///< 16 bit per channel, 1 channel unsigned normalized format
FFX_API_SURFACE_FORMAT_R16_SNORM, ///< 16 bit per channel, 1 channel signed normalized format
FFX_API_SURFACE_FORMAT_R8_UNORM, ///< 8 bit per channel, 1 channel unsigned normalized format
FFX_API_SURFACE_FORMAT_R8G8_UNORM, ///< 8 bit per channel, 2 channel unsigned normalized format
FFX_API_SURFACE_FORMAT_R8G8_UINT, ///< 8 bit per channel, 2 channel unsigned integer format
FFX_API_SURFACE_FORMAT_R32_FLOAT, ///< 32 bit per channel, 1 channel float format
FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP, ///< 9 bit per channel, 5 bit exponent format
FFX_API_SURFACE_FORMAT_R16G16B16A16_TYPELESS, ///< 16 bit per channel, 4 channel typeless format
FFX_API_SURFACE_FORMAT_R32G32_TYPELESS, ///< 32 bit per channel, 2 channel typeless format
FFX_API_SURFACE_FORMAT_R10G10B10A2_TYPELESS, ///< 10 bit per 3 channel, 2 bit for 1 channel typeless format
FFX_API_SURFACE_FORMAT_R16G16_TYPELESS, ///< 16 bit per channel, 2 channel typless format
FFX_API_SURFACE_FORMAT_R16_TYPELESS, ///< 16 bit per channel, 1 channel typeless format
FFX_API_SURFACE_FORMAT_R8_TYPELESS, ///< 8 bit per channel, 1 channel typeless format
FFX_API_SURFACE_FORMAT_R8G8_TYPELESS, ///< 8 bit per channel, 2 channel typeless format
FFX_API_SURFACE_FORMAT_R32_TYPELESS, ///< 32 bit per channel, 1 channel typeless format
FFX_API_SURFACE_FORMAT_R32G32_UINT, ///< 32 bit per channel, 2 channel uint format
FFX_API_SURFACE_FORMAT_R8_SNORM, ///< 8 bit per channel, 1 channel signed normalized format
};
/// An enumeration of resource usage.
enum FfxApiResourceUsage
{
FFX_API_RESOURCE_USAGE_READ_ONLY = 0, ///< No usage flags indicate a resource is read only.
FFX_API_RESOURCE_USAGE_RENDERTARGET = (1<<0), ///< Indicates a resource will be used as render target.
FFX_API_RESOURCE_USAGE_UAV = (1<<1), ///< Indicates a resource will be used as UAV.
FFX_API_RESOURCE_USAGE_DEPTHTARGET = (1<<2), ///< Indicates a resource will be used as depth target.
FFX_API_RESOURCE_USAGE_INDIRECT = (1<<3), ///< Indicates a resource will be used as indirect argument buffer
FFX_API_RESOURCE_USAGE_ARRAYVIEW = (1<<4), ///< Indicates a resource that will generate array views. Works on 2D and cubemap textures
FFX_API_RESOURCE_USAGE_STENCILTARGET = (1<<5), ///< Indicates a resource will be used as stencil target.
FFX_API_RESOURCE_USAGE_DCC_RENDERTARGET = (1<<15), ///< Indicates a resource that should specify optimal render target memory access flags (for console use)
};
/// An enumeration of resource states.
enum FfxApiResourceState
{
FFX_API_RESOURCE_STATE_COMMON = (1 << 0),
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS = (1 << 1), ///< Indicates a resource is in the state to be used as UAV.
FFX_API_RESOURCE_STATE_COMPUTE_READ = (1 << 2), ///< Indicates a resource is in the state to be read by compute shaders.
FFX_API_RESOURCE_STATE_PIXEL_READ = (1 << 3), ///< Indicates a resource is in the state to be read by pixel shaders.
FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ = (FFX_API_RESOURCE_STATE_PIXEL_READ | FFX_API_RESOURCE_STATE_COMPUTE_READ), ///< Indicates a resource is in the state to be read by pixel or compute shaders.
FFX_API_RESOURCE_STATE_COPY_SRC = (1 << 4), ///< Indicates a resource is in the state to be used as source in a copy command.
FFX_API_RESOURCE_STATE_COPY_DEST = (1 << 5), ///< Indicates a resource is in the state to be used as destination in a copy command.
FFX_API_RESOURCE_STATE_GENERIC_READ = (FFX_API_RESOURCE_STATE_COPY_SRC | FFX_API_RESOURCE_STATE_COMPUTE_READ), ///< Indicates a resource is in generic (slow) read state.
FFX_API_RESOURCE_STATE_INDIRECT_ARGUMENT = (1 << 6), ///< Indicates a resource is in the state to be used as an indirect command argument
FFX_API_RESOURCE_STATE_PRESENT = (1 << 7), ///< Indicates a resource is in the state to be used to present to the swap chain
FFX_API_RESOURCE_STATE_RENDER_TARGET = (1 << 8), ///< Indicates a resource is in the state to be used as render target
FFX_API_RESOURCE_STATE_DEPTH_ATTACHMENT = (1 << 9), ///< Indicates a resource is in the state to be used as depth attachment
};
/// An enumeration of surface dimensions.
enum FfxApiResourceDimension
{
FFX_API_RESOURCE_DIMENSION_TEXTURE_1D, ///< A resource with a single dimension.
FFX_API_RESOURCE_DIMENSION_TEXTURE_2D, ///< A resource with two dimensions.
};
/// An enumeration of resource flags.
enum FfxApiResourceFlags
{
FFX_API_RESOURCE_FLAGS_NONE = 0, ///< No flags.
FFX_API_RESOURCE_FLAGS_ALIASABLE = (1 << 0), ///< A bit indicating a resource does not need to persist across frames.
FFX_API_RESOURCE_FLAGS_UNDEFINED = (1 << 1), ///< Special case flag used internally when importing resources that require additional setup
};
// An enumeration for different resource types
enum FfxApiResourceType
{
FFX_API_RESOURCE_TYPE_BUFFER, ///< The resource is a buffer.
FFX_API_RESOURCE_TYPE_TEXTURE1D, ///< The resource is a 1-dimensional texture.
FFX_API_RESOURCE_TYPE_TEXTURE2D, ///< The resource is a 2-dimensional texture.
FFX_API_RESOURCE_TYPE_TEXTURE_CUBE, ///< The resource is a cube map.
FFX_API_RESOURCE_TYPE_TEXTURE3D, ///< The resource is a 3-dimensional texture.
};
enum FfxApiBackbufferTransferFunction
{
FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB,
FFX_API_BACKBUFFER_TRANSFER_FUNCTION_PQ,
FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SCRGB
};
/// A structure encapsulating a 2-dimensional point, using 32bit unsigned integers.
struct FfxApiDimensions2D
{
uint32_t width; ///< The width of a 2-dimensional range.
uint32_t height; ///< The height of a 2-dimensional range.
};
/// A structure encapsulating a 2-dimensional set of floating point coordinates.
struct FfxApiFloatCoords2D
{
float x; ///< The x coordinate of a 2-dimensional point.
float y; ///< The y coordinate of a 2-dimensional point.
};
/// A structure encapsulating a 3-dimensional set of floating point coordinates.
struct FfxApiFloatCoords3D
{
float x; ///< The x coordinate of a 3-dimensional point.
float y; ///< The y coordinate of a 3-dimensional point.
float z; ///< The z coordinate of a 3-dimensional point.
};
/// A structure encapsulating a 2-dimensional rect.
struct FfxApiRect2D
{
int32_t left;
int32_t top;
int32_t width;
int32_t height;
};
/// A structure describing a resource.
///
/// @ingroup SDKTypes
struct FfxApiResourceDescription
{
uint32_t type; ///< The type of the resource.
uint32_t format; ///< The surface format.
union {
uint32_t width; ///< The width of the texture resource.
uint32_t size; ///< The size of the buffer resource.
};
union {
uint32_t height; ///< The height of the texture resource.
uint32_t stride; ///< The stride of the buffer resource.
};
union {
uint32_t depth; ///< The depth of the texture resource.
uint32_t alignment; ///< The alignment of the buffer resource.
};
uint32_t mipCount; ///< Number of mips (or 0 for full mipchain).
uint32_t flags; ///< A set of resource flags.
uint32_t usage; ///< Resource usage flags.
};
#define FFX_RESOURCE_NAME_SIZE 64
struct FfxApiResource
{
void* resource;
struct FfxApiResourceDescription description;
uint32_t state;
};
//struct definition matches FfxApiEffectMemoryUsage
typedef struct FfxApiEffectMemoryUsage
{
uint64_t totalUsageInBytes;
uint64_t aliasableUsageInBytes;
} FfxApiEffectMemoryUsage;
/// A structure describing a constant buffer allocation.
///
/// @ingroup SDKTypes
typedef struct FfxApiConstantBufferAllocation
{
struct FfxApiResource resource; ///< The resource representing the constant buffer resource.
uint64_t handle; ///< The binding handle for the constant buffer
} FfxApiConstantBufferAllocation;
/// A function definition for a constant buffer allocation callback
/// Used to provide a constant buffer allocator to the calling backend
///
/// @param [in] data The constant buffer data.
/// @param [in] dataSize The size of the constant buffer data.
///
/// @ingroup SDKTypes
typedef FfxApiConstantBufferAllocation(*FfxApiConstantBufferAllocator)(void* data, const uint64_t dataSize);
/// A 4-component floating point vector
typedef struct FfxApiFloat4
{
float x;
float y;
float z;
float w;
} FfxApiFloat4;
/// A 4x4 matrix in row-major layout with row-vector convention.
/// This is the canonical format expected by FFX APIs.
///
/// Memory layout: rows[row_index][column_index]
/// Mathematical convention: v * M (row vector multiplied by matrix)
typedef struct FfxApiMatrix4x4
{
FfxApiFloat4 rows[4]; ///< Four rows of the matrix. Access as rows[row].x, rows[row].y etc.
} FfxApiMatrix4x4;
/// An inclusive floating-point interval [min, max].
/// Valid instances must satisfy min <= max.
typedef struct FfxApiFloatBounds
{
float min; ///< Lower bound of the interval.
float max; ///< Upper bound of the interval.
} FfxApiFloatBounds;
+584
View File
@@ -0,0 +1,584 @@
// This file is part of the FidelityFX SDK.
//
// Copyright (C) 2026 Advanced Micro Devices, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#pragma once
//------------------------------------------------------------------------------
// FFX Includes
//------------------------------------------------------------------------------
// FFX_API_EFFECT_ID_DENOISER
// FFX_API_MAKE_EFFECT_SUB_ID
// ffxConfigureDescHeader
// ffxCreateContextDescHeader
// ffxDispatchDescHeader
// ffxQueryDescHeader
#include "../../api/include/ffx_api.h"
// FfxApiDimensions2D
// FfxApiFloatBounds
// FfxApiFloatCoords2D
// FfxApiFloatCoords3D
// FfxApiMatrix4x4
// FfxApiResource
#include "../../api/include/ffx_api_types.h"
//------------------------------------------------------------------------------
// External Includes
//------------------------------------------------------------------------------
// uint32_t
// uint64_t
#include <stdint.h>
//------------------------------------------------------------------------------
// FFX Denoiser Defines
//------------------------------------------------------------------------------
/// Denoiser major version number
#define FFX_DENOISER_VERSION_MAJOR 1
/// Denoiser minor version number
#define FFX_DENOISER_VERSION_MINOR 2
/// Denoiser pinor version number
#define FFX_DENOISER_VERSION_PATCH 0
#define FFX_DENOISER_MAKE_VERSION(major, minor, patch) (((major) << 22) | ((minor) << 12) | (patch))
/// Denoiser combined version number
#define FFX_DENOISER_VERSION FFX_DENOISER_MAKE_VERSION(FFX_DENOISER_VERSION_MAJOR, FFX_DENOISER_VERSION_MINOR, FFX_DENOISER_VERSION_PATCH)
//------------------------------------------------------------------------------
// FFX Denoiser Declarations
//------------------------------------------------------------------------------
#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
//------------------------------------------------------------------------------
// FFX Denoiser Descriptions: Create Context
//------------------------------------------------------------------------------
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_DENOISER FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x01)
/// Denoiser description for context creation.
typedef struct ffxCreateContextDescDenoiser
{
/// Header description
/// * @c type: FFX_API_CREATE_CONTEXT_DESC_TYPE_DENOISER
ffxCreateContextDescHeader header;
/// The version of the API the application was built against.
/// Must be set to @c FFX_DENOISER_VERSION.
uint32_t version;
/// Maximum size that rendering will be performed at.
struct FfxApiDimensions2D maxRenderSize;
/// Signal flags corresponding to a combination of values from @c FfxApiDenoiserSignalFlags.
uint32_t signalFlags;
/// Signal flags corresponding to a combination of values from @c FfxApiDenoiserSignalFlags
/// for signals that use checkerboard reconstruction.
/// Must be a subset of @c signalFlags.
uint32_t checkerboardSignalFlags;
/// Create flags corresponding to zero or a combination of values from @c FfxApiCreateContextDenoiserFlags.
uint32_t flags;
} ffxCreateContextDescDenoiser;
//------------------------------------------------------------------------------
// FFX Denoiser Descriptions: Configure
//------------------------------------------------------------------------------
#define FFX_API_CONFIGURE_DESC_TYPE_DENOISER_KEYVALUE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x21)
/// Denoiser description for configuring.
/// @pre Required to be passed to @c ffxConfigure with a non-nullptr denoiser @c FfxContext*.
typedef struct ffxConfigureDescDenoiserKeyValue
{
/// Header description
/// * @c type: FFX_API_CONFIGURE_DESC_TYPE_DENOISER_KEYVALUE
ffxConfigureDescHeader header;
/// Configuration key corresponding to an entry of @c FfxApiConfigureDenoiserKey.
uint64_t key;
/// The number of elements to configure.
uint64_t count;
/// Pointer to an array with the configuration values for the elements to configure.
const void* data;
} ffxConfigureDescDenoiserKeyValue;
//------------------------------------------------------------------------------
// FFX Denoiser Descriptions: Dispatch
//------------------------------------------------------------------------------
typedef struct FfxApiDenoiserSignal
{
/// Input signal to be denoised.
struct FfxApiResource input;
/// Resulting denoised signal.
struct FfxApiResource output;
/// @brief The X-coordinate of the first traced pixel at Y=0 in a checkerboard pattern.
///
/// In a checkerboard pattern, only half the pixels are traced per frame, alternating as follows:
///
/// @code
/// x=0 x=1 x=2 x=3
/// y=0: 0 1 0 1
/// y=1: 1 0 1 0
/// y=2: 0 1 0 1
/// @endcode
///
/// * @c 0: pixel (0, 0) was traced.
/// * @c 1: pixel (1, 0) was traced.
///
/// @code checkerboardOrigin = frameIndex & 1 @endcode
///
/// @pre Must be zero-initialized if this signal is not set in @c checkerboardSignalFlags.
/// @pre Only meaningful if this signal is set in @c checkerboardSignalFlags.
uint32_t checkerboardOrigin;
} FfxApiDenoiserSignal;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x41)
/// Denoiser description for dispatching.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Always required to be passed to @c ffxDispatch.
typedef struct ffxDispatchDescDenoiser
{
/// Header description
///
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER
ffxDispatchDescHeader header;
/// A pointer to the command list.
///
/// * DX12: pointer to ID3D12GraphicsCommandList.
void* commandList;
/// @brief The signed linear depth.
///
/// Channels:
/// * R: Signed linear depth values for the current frame. @code CurrentLinearDepth @endcode
struct FfxApiResource linearDepth;
/// @brief The motion vectors.
///
/// Channels:
/// * RG: 2D motion vectors. @code PreviousUV - CurrentUV @endcode
/// * B: Signed linear depth delta. @code PreviousLinearDepth - CurrentLinearDepth @endcode
struct FfxApiResource motionVectors;
/// @brief The normals, roughness and material type.
///
/// Channels:
/// * RG: Octahedrally encoded normals
/// * B: Linear Roughness
/// * A: Material Type - See docs for more info,
struct FfxApiResource normals;
/// @brief The specular albedo.
///
/// Channels:
/// * RGB: Specular albedo - sqrt encoding assumed unless @c FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO is provided. @code sqrt(SpecularAlbedo) @endcode
/// @pre Can be zero-initialized if and only if @c FFX_DENOISER_SIGNAL_DIRECT_SPECULAR, @c FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR, and @c FFX_DENOISER_SIGNAL_DOMINANT_LIGHT_VISIBILITY are not set in the context creation signal flags.
struct FfxApiResource specularAlbedo;
/// @brief The diffuse albedo.
///
/// Channels:
/// * RGB: Diffuse albedo (e.g., @code BaseColor * (1 - Metalness)) @endcode - sqrt encoding assumed unless @c FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO is provided. @code sqrt(DiffuseAlbedo) @endcode
/// @pre Can be zero-initialized if and only if @c FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE, @c FFX_DENOISER_SIGNAL_INDIRECT_DIFFUSE, and @c FFX_DENOISER_SIGNAL_DOMINANT_LIGHT_VISIBILITY are not set in the context creation signal flags.
struct FfxApiResource diffuseAlbedo;
/// @brief The motion vector scale factor.
///
/// Channels:
/// * RG: The scale factor for transforming the 2D motion vectors into UV space
/// * For 2D motion vectors computed as @code PreviousUV - CurrentUV @endcode, use @code { .x = +1.0f, .y = +1.0f } @endcode.
/// * For 2D motion vectors computed as @code PreviousNDC - CurrentNDC @endcode, use @code { .x = +0.5f, .y = -0.5f } @endcode.
/// * B: The scale factor for transforming the signed linear depth delta
/// * For linear depth deltas computed as @code PreviousLinearDepth - CurrentLinearDepth @endcode, use @code { .z = +1.0f } @endcode.
struct FfxApiFloatCoords3D motionVectorScale;
/// The subpixel jitter offset applied to the camera projection. (Expressed in screen pixels)
struct FfxApiFloatCoords2D jitterOffsets;
/// The position delta of the camera since last frame. @code PreviousPosition - CurrentPosition @endcode (Expressed in world space)
struct FfxApiFloatCoords3D cameraPositionDelta;
/// @brief The world-to-view transformation matrix for the current frame.
///
/// Matrix convention:
/// * Storage: row-major (rows stored contiguously in memory)
/// * Multiplication: row vectors @code v' = vM @endcode
///
/// Compatibility:
/// * Column-major with column vectors (e.g., OpenGL, Cauldron):
/// direct copy (memcpy) - no transpose needed
/// * Column-major with row vectors:
/// requires transpose
/// * Row-major with row vectors (e.g., DirectXMath):
/// direct copy (memcpy)
/// * Row-major with column vectors:
/// requires transpose
///
/// Memory Layout Example (translation by (Tx, Ty, Tz)):
/// [m00 m01 m02 m03] [1 0 0 0]
/// [m10 m11 m12 m13] = [0 1 0 0]
/// [m20 m21 m22 m23] [0 0 1 0]
/// [m30 m31 m32 m33] [Tx Ty Tz 1]
FfxApiMatrix4x4 view;
/// @brief The (unjittered) view-to-projection transformation matrix for the current frame.
///
/// Matrix convention:
/// * Storage: row-major (rows stored contiguously in memory)
/// * Multiplication: row vectors @code v' = vM @endcode
///
/// Compatibility:
/// * Column-major with column vectors (e.g., OpenGL, Cauldron):
/// direct copy (memcpy) - no transpose needed
/// * Column-major with row vectors:
/// requires transpose
/// * Row-major with row vectors (e.g., DirectXMath):
/// direct copy (memcpy)
/// * Row-major with column vectors:
/// requires transpose
FfxApiMatrix4x4 projection;
/// @brief The absolute linear depth bounds considered for denoising.
/// @post Passthrough is enabled for signal texels where the corresponding absolute linear depth values are outside [linearDepthBounds.min,linearDepthBounds.max].
FfxApiFloatBounds linearDepthBounds;
/// The resolution that was used for rendering the input resources.
struct FfxApiDimensions2D renderSize;
/// The index of the current frame.
uint32_t frameIndex;
/// Dispatch flags corresponding to zero or a combination of values from @c FfxApiDispatchDenoiserFlags.
uint32_t flags;
} ffxDispatchDescDenoiser;
/// The maximum number of viewports that can be visualized.
#define FFX_API_DENOISER_DEBUG_VIEW_MAX_VIEWPORTS 12
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_DEBUG_VIEW FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x42)
/// Denoiser description for dispatching debug views.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Can be passed to @c ffxDispatch if and only if @c FFX_DENOISER_ENABLE_DEBUGGING is set in the context creation flags.
typedef struct ffxDispatchDescDenoiserDebugView
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_DEBUG_VIEW
ffxDispatchDescHeader header;
/// @brief The target output resource for debug visualization.
///
/// Channels:
/// * RGB: Color information to display.
/// * A: Indicates which pixels have been written to.
struct FfxApiResource output;
/// The resolution of the debug visualization.
struct FfxApiDimensions2D outputSize;
/// The debug view mode corresponding to an entry of @c FfxApiDenoiserDebugViewMode.
uint32_t mode;
/// The index of the viewport to visualize when @c FFX_API_DENOISER_DEBUG_VIEW_MODE_FULLSCREEN_VIEWPORT mode is active.
/// Clamped between 0 and @c FFX_API_DENOISER_DEBUG_VIEW_MAX_VIEWPORTS - 1.
uint32_t viewportIndex;
} ffxDispatchDescDenoiserDebugView;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_AMBIENT_OCCLUSION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x43)
/// Denoiser description for dispatching ambient occlusion signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_SIGNAL_AMBIENT_OCCLUSION is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserAmbientOcclusion
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_AMBIENT_OCCLUSION
ffxDispatchDescHeader header;
/// Channels:
/// * R:
/// * @c input: Ambient occlusion (in [0,1] where 0 is fully occluded and 1 is fully exposed) to denoise
/// * @c output: Denoised ambient occlusion (in [0,1] where 0 is fully occluded and 1 is fully exposed) (or input if passthrough)
struct FfxApiDenoiserSignal signal;
} ffxDispatchDescDenoiserAmbientOcclusion;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x44)
/// Denoiser description for dispatching direct diffuse signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserDirectDiffuse
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE
ffxDispatchDescHeader header;
/// Channels:
/// * RGB:
/// * @c input: Direct diffuse radiance to denoise
/// * @c output: Denoised direct diffuse radiance (or input if passthrough)
/// * A:
/// * @c input: Undefined
/// * @c output: Preserved (or input if passthrough)
struct FfxApiDenoiserSignal signal;
} ffxDispatchDescDenoiserDirectDiffuse;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_SPECULAR FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x45)
/// Denoiser description for dispatching direct specular signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_SIGNAL_DIRECT_SPECULAR is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserDirectSpecular
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_SPECULAR
ffxDispatchDescHeader header;
/// Channels:
/// * RGB:
/// * @c input: Direct specular radiance to denoise
/// * @c output: Denoised direct specular radiance (or input if passthrough)
/// * A:
/// * @c input: Undefined
/// * @c output: Preserved (or input if passthrough)
struct FfxApiDenoiserSignal signal;
} ffxDispatchDescDenoiserDirectSpecular;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_DOMINANT_LIGHT FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x46)
/// Denoiser description for dispatching dominant light visibility signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_SIGNAL_DOMINANT_LIGHT_VISIBILITY is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserDominantLight
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_DOMINANT_LIGHT
ffxDispatchDescHeader header;
/// Channels:
/// * R:
/// * @c input: Dominant light visibility (ray hit distance [0,FP16_MAX] where FP16_MAX is fully exposed) to denoise
/// * @c output: Denoised dominant light visibility (in [0,1] where 0 is fully occluded and 1 is fully exposed) (or input if passthrough)
struct FfxApiDenoiserSignal signal;
/// Dominant light direction. (from light source to target)
struct FfxApiFloatCoords3D direction;
/// Dominant light emission. (i.e. @c LightColor * LightIntensity)
struct FfxApiFloatCoords3D emission;
/// Dominant light angular radius. (Expressed in radians)
float angularRadius;
} ffxDispatchDescDenoiserDominantLight;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x47)
/// Denoiser description for dispatching indirect diffuse signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_SIGNAL_INDIRECT_DIFFUSE is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserIndirectDiffuse
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE
ffxDispatchDescHeader header;
/// Channels:
/// * RGB:
/// * @c input: Indirect diffuse radiance to denoise
/// * @c output: Denoised indirect diffuse radiance (or input if passthrough)
/// * A:
/// * @c input: Indirect diffuse ray hit distance
/// * @c output: Preserved (or input if passthrough)
struct FfxApiDenoiserSignal signal;
} ffxDispatchDescDenoiserIndirectDiffuse;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x48)
/// Denoiser description for dispatching of indirect specular signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserIndirectSpecular
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR
ffxDispatchDescHeader header;
/// Channels:
/// * RGB:
/// * @c input: Indirect specular radiance to denoise
/// * @c output: Denoised indirect specular radiance (or input if passthrough)
/// * A:
/// * @c input: Indirect specular ray hit distance
/// * @c output: Preserved (or input if passthrough)
struct FfxApiDenoiserSignal signal;
} ffxDispatchDescDenoiserIndirectSpecular;
#define FFX_API_DISPATCH_DESC_TYPE_DENOISER_SPECULAR_OCCLUSION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x49)
/// Denoiser description for dispatching specular occlusion signals.
/// @pre Required to be passed to @c ffxDispatch with a non-nullptr denoiser @c FfxContext*.
/// @pre Required to be passed to @c ffxDispatch if and only if @c FFX_DENOISER_ENABLE_SPECULAR_OCCLUSION is set in the context creation signal flags.
typedef struct ffxDispatchDescDenoiserSpecularOcclusion
{
/// Header description
/// * @c type: FFX_API_DISPATCH_DESC_TYPE_DENOISER_SPECULAR_OCCLUSION
ffxDispatchDescHeader header;
/// Channels:
/// * R:
/// * @c input: Specular occlusion (in [0,1], where 0 is fully occluded and 1 is fully exposed) to denoise
/// * @c output: Denoised specular occlusion (in [0,1], where 0 is fully occluded and 1 is fully exposed) (or input if passthrough)
struct FfxApiDenoiserSignal signal;
} ffxDispatchDescDenoiserSpecularOcclusion;
//------------------------------------------------------------------------------
// FFX Denoiser Descriptions: Query
//------------------------------------------------------------------------------
#define FFX_API_QUERY_DESC_TYPE_DENOISER_GET_DEFAULT_KEYVALUE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x81)
/// Denoiser description for querying default configuration values.
/// @pre Required to be passed to @c ffxQuery with a non-nullptr denoiser @c FfxContext*.
typedef struct ffxQueryDescDenoiserGetDefaultKeyValue
{
/// Header description
/// * @c type: FFX_API_QUERY_DESC_TYPE_DENOISER_GET_DEFAULT_KEYVALUE
ffxQueryDescHeader header;
/// Configuration key corresponding to an entry of @c FfxApiConfigureDenoiserKey.
uint64_t key;
/// The number of elements to query.
uint64_t count;
/// Pointer to an array for storing the queried elements.
void* data;
} ffxQueryDescDenoiserGetDefaultKeyValue;
#define FFX_API_QUERY_DESC_TYPE_DENOISER_GPU_MEMORY_USAGE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_DENOISER, 0x82)
/// Denoiser description for querying GPU memory usage.
/// @pre Can be passed to @c ffxQuery with a non-nullptr denoiser @c FfxContext* to retrieve the actual memory usage.
/// @pre Can be passed to @c ffxQuery with a nullptr @c FfxContext* to retrieve the expected memory usage.
typedef struct ffxQueryDescDenoiserGetGPUMemoryUsage
{
/// Header description
/// * @c type: FFX_API_QUERY_DESC_TYPE_DENOISER_GPU_MEMORY_USAGE
ffxQueryDescHeader header;
/// A pointer to the device.
/// * DX12: pointer to ID3D12Device.
void* device;
/// Maximum size that rendering will be performed at.
struct FfxApiDimensions2D maxRenderSize;
/// Signal flags corresponding to a combination of values from @c FfxApiDenoiserSignalFlags.
uint32_t signalFlags;
/// Signal flags corresponding to a combination of values from @c FfxApiDenoiserSignalFlags
/// for signals that use checkerboard reconstruction.
/// Must be a subset of @c signalFlags.
uint32_t checkerboardSignalFlags;
/// Create flags corresponding to zero or a combination of values from @c FfxApiCreateContextDenoiserFlags.
uint32_t flags;
/// A pointer to a @c FfxApiEffectMemoryUsage structure that will hold the GPU memory usage.
struct FfxApiEffectMemoryUsage* gpuMemoryUsage;
} ffxQueryDescDenoiserGetGPUMemoryUsage;
//------------------------------------------------------------------------------
// FFX Denoiser Enums
//------------------------------------------------------------------------------
typedef enum FfxApiCreateContextDenoiserFlags
{
/// Flag indicating that debug features may be enabled. Memory usage may increase.
FFX_DENOISER_ENABLE_DEBUGGING = (1 << 0),
/// Flag indicating that exhaustive validation should be enabled. Performance may decrease.
FFX_DENOISER_ENABLE_VALIDATION = (1 << 1),
} FfxApiCreateContextDenoiserFlags;
typedef enum FfxApiConfigureDenoiserKey
{
/// Override the strength of the cross bilateral normal term.
/// * Format: A single @c float.
FFX_API_CONFIGURE_DENOISER_KEY_CROSS_BILATERAL_NORMAL_STRENGTH = 1,
/// Override the bias of the temporal accumulation to be more stable but less responsive.
/// * Format: A single @c float.
FFX_API_CONFIGURE_DENOISER_KEY_STABILITY_BIAS = 2,
/// Override the maximum radiance value. A single float scalar.
/// * Format: A single @c float.
FFX_API_CONFIGURE_DENOISER_KEY_MAX_RADIANCE = 3,
/// Override the standard deviation K value used for radiance clipping.
/// * Format: A single @c float.
FFX_API_CONFIGURE_DENOISER_KEY_RADIANCE_CLIP_STD_K = 4,
/// Override the Gaussian kernel relaxation factor.
/// * Format: A single @c float.
FFX_API_CONFIGURE_DENOISER_KEY_GAUSSIAN_KERNEL_RELAXATION = 5,
/// Override the discocclusion threshold used for depth comparisons during temporal reprojection.
/// * Format: A single @c float.
FFX_API_CONFIGURE_DENOISER_KEY_DISOCCLUSION_THRESHOLD = 6,
/// Override the absolute linear depth bounds used for normalizing the absolute linear depth debug view.
/// * Format: A single @c FfxApiFloatBounds.
FFX_API_CONFIGURE_DENOISER_KEY_DEBUG_VIEW_LINEAR_DEPTH_BOUNDS = 7,
} FfxApiConfigureDenoiserKey;
typedef enum FfxApiDenoiserDebugViewMode
{
/// Overview debug view mode.
FFX_API_DENOISER_DEBUG_VIEW_MODE_OVERVIEW = 0,
/// Fullscreen viewport debug view mode.
FFX_API_DENOISER_DEBUG_VIEW_MODE_FULLSCREEN_VIEWPORT = 1,
} FfxApiDenoiserDebugViewMode;
typedef enum FfxApiDenoiserSignalFlags
{
FFX_DENOISER_SIGNAL_NONE = 0,
/// Flag indicating that ambient occlusion needs to be denoised.
FFX_DENOISER_SIGNAL_AMBIENT_OCCLUSION = (1 << 0),
/// Flag indicating that direct diffuse needs to be denoised.
FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE = (1 << 1),
/// Flag indicating that direct specular needs to be denoised.
FFX_DENOISER_SIGNAL_DIRECT_SPECULAR = (1 << 2),
/// Flag indicating that dominant light visibility needs to be denoised.
FFX_DENOISER_SIGNAL_DOMINANT_LIGHT_VISIBILITY = (1 << 3),
/// Flag indicating that indirect diffuse needs to be denoised.
FFX_DENOISER_SIGNAL_INDIRECT_DIFFUSE = (1 << 4),
/// Flag indicating that indirect specular needs to be denoised.
FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR = (1 << 5),
/// Flag indicating that specular occlusion needs to be denoised.
FFX_DENOISER_SIGNAL_SPECULAR_OCCLUSION = (1 << 6),
} FfxApiDenoiserSignalFlags;
typedef enum FfxApiDispatchDenoiserFlags
{
/// Flag indicating that the history accumulation needs to be reset.
FFX_DENOISER_DISPATCH_RESET = (1 << 0),
/// Flag indicating that the (diffuse and specular) albedo textures are not gamma encoded.
FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO = (1 << 1),
} FfxApiDispatchDenoiserFlags;
#ifdef __cplusplus
}
#endif // __cplusplus
+279
View File
@@ -0,0 +1,279 @@
// AMD FSR Ray Regeneration (FidelityFX denoiser 1.2) runtime access, shared by the NGX shim's
// FSR Ray Reconstruction backend and tools/fsr_rr_probe.cpp.
//
// The denoiser is AMD's signed amd_fidelityfx_denoiser_dx12.dll, loaded from an explicit path
// (never from a search path, never patched) and driven through its five FFX API exports. It
// records its compute work into the caller's D3D12 command list; nothing here involves CUDA,
// ZLUDA or NVIDIA's denoiser. Struct layouts come from AMD's MIT-licensed headers in
// third_party/fidelityfx (SDK 2.3.0 / FSR Ray Regeneration 1.2.0).
//
// Each stage reports separately (see FsrRrStage): a missing DLL, a missing export, a provider
// query that returns no denoiser for this device, a failed context creation (where shader
// pipelines are built) and a failed dispatch are different failures and are never folded together.
#pragma once
#include <windows.h>
#include <d3d12.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "../third_party/fidelityfx/api/include/ffx_api.h"
#include "../third_party/fidelityfx/api/include/ffx_api_types.h"
#include "../third_party/fidelityfx/denoisers/include/ffx_denoiser.h"
// ffx_api_dx12.h's backend descriptor, declared here because that header's inline helpers call
// ID3D12Resource::GetDesc() by value, which does not compile with WIDL_EXPLICIT_AGGREGATE_RETURNS.
#ifndef FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02)
struct ffxCreateBackendDX12Desc
{
ffxCreateContextDescHeader header;
ID3D12Device* device;
};
#endif
static_assert(sizeof(ffxCreateBackendDX12Desc) == 24, "ffx_api_dx12.h layout");
// The 1.2 descriptors as the signed runtime reads them (x64, natural alignment, same as MSVC).
static_assert(sizeof(FfxApiResource) == 48 && sizeof(FfxApiDenoiserSignal) == 104, "ffx_api_types.h layout");
static_assert(sizeof(ffxCreateContextDescDenoiser) == 40, "ffx_denoiser.h 1.2 create layout");
static_assert(sizeof(ffxDispatchDescDenoiser) == 448 && offsetof(ffxDispatchDescDenoiser, view) == 296 &&
offsetof(ffxDispatchDescDenoiser, renderSize) == 432,
"ffx_denoiser.h 1.2 dispatch layout");
enum class FsrRrStage
{
NotLoaded, // nothing tried yet
DllMissing, // no file at the configured path
DllLoadFailed, // LoadLibrary failed (dependencies, architecture)
ExportsMissing, // not an FFX API DLL
NoProvider, // the runtime returned no denoiser version for this device
Ready, // a provider was enumerated for this device; contexts may still fail
};
inline const char* fsr_rr_stage_name(FsrRrStage stage)
{
switch (stage)
{
case FsrRrStage::NotLoaded: return "not loaded";
case FsrRrStage::DllMissing: return "denoiser DLL missing";
case FsrRrStage::DllLoadFailed: return "denoiser DLL failed to load";
case FsrRrStage::ExportsMissing: return "denoiser DLL lacks the FFX API exports";
case FsrRrStage::NoProvider: return "no FSR Ray Regeneration provider for this device";
case FsrRrStage::Ready: return "provider available";
}
return "?";
}
inline const char* fsr_rr_return_name(ffxReturnCode_t code)
{
switch (code)
{
case FFX_API_RETURN_OK: return "OK";
case FFX_API_RETURN_ERROR: return "ERROR";
case FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE: return "ERROR_UNKNOWN_DESCTYPE";
case FFX_API_RETURN_ERROR_RUNTIME_ERROR: return "ERROR_RUNTIME_ERROR";
case FFX_API_RETURN_NO_PROVIDER: return "NO_PROVIDER";
case FFX_API_RETURN_ERROR_MEMORY: return "ERROR_MEMORY";
case FFX_API_RETURN_ERROR_PARAMETER: return "ERROR_PARAMETER";
case FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE: return "PROVIDER_NO_SUPPORT_NEW_DESCTYPE";
}
return "unknown";
}
// The loaded runtime and the provider the device was offered. One per process: the DLL keeps
// global state (its debug callback), so it is loaded once and never unloaded while contexts exist.
struct FsrRrRuntime
{
HMODULE module = nullptr;
std::wstring path; // as configured
std::wstring mappedPath; // as the loader mapped it
PfnFfxCreateContext createContext = nullptr;
PfnFfxDestroyContext destroyContext = nullptr;
PfnFfxConfigure configure = nullptr;
PfnFfxQuery query = nullptr;
PfnFfxDispatch dispatch = nullptr;
FsrRrStage stage = FsrRrStage::NotLoaded;
ffxReturnCode_t queryResult = FFX_API_RETURN_OK;
std::vector<uint64_t> versionIds;
std::vector<std::string> versionNames;
uint64_t providerId = 0; // the provider contexts are created with (the first enumerated)
std::string providerName;
// Loads `dll` with its own directory searched for dependencies. Does not query providers.
bool load(const std::wstring& dll)
{
path = dll;
if (GetFileAttributesW(dll.c_str()) == INVALID_FILE_ATTRIBUTES)
{
stage = FsrRrStage::DllMissing;
return false;
}
module = LoadLibraryExW(dll.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
if (module == nullptr)
{
stage = FsrRrStage::DllLoadFailed;
return false;
}
wchar_t mapped[MAX_PATH] = {};
GetModuleFileNameW(module, mapped, MAX_PATH);
mappedPath = mapped;
createContext = reinterpret_cast<PfnFfxCreateContext>(reinterpret_cast<void*>(GetProcAddress(module, "ffxCreateContext")));
destroyContext = reinterpret_cast<PfnFfxDestroyContext>(reinterpret_cast<void*>(GetProcAddress(module, "ffxDestroyContext")));
configure = reinterpret_cast<PfnFfxConfigure>(reinterpret_cast<void*>(GetProcAddress(module, "ffxConfigure")));
query = reinterpret_cast<PfnFfxQuery>(reinterpret_cast<void*>(GetProcAddress(module, "ffxQuery")));
dispatch = reinterpret_cast<PfnFfxDispatch>(reinterpret_cast<void*>(GetProcAddress(module, "ffxDispatch")));
if (!createContext || !destroyContext || !configure || !query || !dispatch)
{
stage = FsrRrStage::ExportsMissing;
return false;
}
return true;
}
// Routes the runtime's own error/warning messages to `callback` (may be called on any thread).
ffxReturnCode_t set_message_callback(ffxApiMessage callback, uint32_t level)
{
ffxConfigureDescGlobalDebug desc = {};
desc.header.type = FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG;
desc.effectId = FFX_API_EFFECT_ID_DENOISER;
desc.fpMessage = callback;
desc.debugLevel = level;
return configure(nullptr, &desc.header);
}
// The two-call version enumeration AMD's sample uses, against the real device. Zero
// versions is the "no provider for this device" outcome, distinct from a query error.
bool query_providers(ID3D12Device* device)
{
versionIds.clear();
versionNames.clear();
uint64_t count = 0;
ffxQueryDescGetVersions desc = {};
desc.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS;
desc.createDescType = FFX_API_EFFECT_ID_DENOISER;
desc.device = device;
desc.outputCount = &count;
queryResult = query(nullptr, &desc.header);
if (queryResult != FFX_API_RETURN_OK || count == 0)
{
stage = FsrRrStage::NoProvider;
return false;
}
versionIds.resize(count);
std::vector<const char*> names(count, nullptr);
desc.versionIds = versionIds.data();
desc.versionNames = names.data();
queryResult = query(nullptr, &desc.header);
if (queryResult != FFX_API_RETURN_OK || count == 0)
{
stage = FsrRrStage::NoProvider;
return false;
}
versionIds.resize(count);
for (uint64_t index = 0; index < count; ++index)
versionNames.emplace_back(names[index] != nullptr ? names[index] : "");
providerId = versionIds[0];
providerName = versionNames[0];
stage = FsrRrStage::Ready;
return true;
}
};
// GPU memory the denoiser would allocate for this configuration (total, aliasable).
inline ffxReturnCode_t fsr_rr_query_memory(FsrRrRuntime& runtime, ID3D12Device* device, uint32_t width,
uint32_t height, uint32_t signals, uint32_t flags,
FfxApiEffectMemoryUsage& usage)
{
ffxOverrideVersion version = {};
version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
version.versionId = runtime.providerId;
ffxQueryDescDenoiserGetGPUMemoryUsage desc = {};
desc.header.type = FFX_API_QUERY_DESC_TYPE_DENOISER_GPU_MEMORY_USAGE;
desc.header.pNext = &version.header;
desc.device = device;
desc.maxRenderSize = {width, height};
desc.signalFlags = signals;
desc.flags = flags;
desc.gpuMemoryUsage = &usage;
return runtime.query(nullptr, &desc.header);
}
// Creates one denoiser context on the enumerated provider. The descriptor chain only has to live
// for the call: ffx_api.h requires pointers *inside* descriptors to outlive the context, and
// none of these carry any.
inline ffxReturnCode_t fsr_rr_create_context(FsrRrRuntime& runtime, ID3D12Device* device, uint32_t maxWidth,
uint32_t maxHeight, uint32_t signals, uint32_t flags,
ffxContext& context)
{
ffxCreateContextDescDenoiser desc = {};
desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_DENOISER;
desc.version = FFX_DENOISER_VERSION;
desc.maxRenderSize = {maxWidth, maxHeight};
desc.signalFlags = signals;
desc.flags = flags;
ffxCreateBackendDX12Desc backend = {};
backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12;
backend.device = device;
ffxOverrideVersion version = {};
version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
version.versionId = runtime.providerId;
desc.header.pNext = &backend.header;
backend.header.pNext = &version.header;
context = nullptr;
return runtime.createContext(&context, &desc.header, nullptr);
}
// Asks a live context which provider actually serves it (the runtime's own account, logged as
// the "actual backend").
inline std::string fsr_rr_context_provider(FsrRrRuntime& runtime, ffxContext& context, uint64_t* id = nullptr)
{
ffxQueryGetProviderVersion desc = {};
desc.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
if (runtime.query(&context, &desc.header) != FFX_API_RETURN_OK || desc.versionName == nullptr)
return std::string();
if (id != nullptr)
*id = desc.versionId;
return desc.versionName;
}
// An FfxApiResource for a 2D texture whose description the caller already knows (the shim never
// calls GetDesc() by value; see above).
inline FfxApiResource fsr_rr_texture(ID3D12Resource* resource, const D3D12_RESOURCE_DESC& desc, uint32_t format,
uint32_t state)
{
FfxApiResource result = {};
result.resource = resource;
result.state = state;
if (resource == nullptr)
return result;
result.description.type = FFX_API_RESOURCE_TYPE_TEXTURE2D;
result.description.format = format;
result.description.width = static_cast<uint32_t>(desc.Width);
result.description.height = desc.Height;
result.description.depth = desc.DepthOrArraySize;
result.description.mipCount = desc.MipLevels;
result.description.usage = FFX_API_RESOURCE_USAGE_READ_ONLY;
if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
result.description.usage |= FFX_API_RESOURCE_USAGE_UAV;
return result;
}
// The FFX surface formats the adapter's own intermediate textures use.
inline uint32_t fsr_rr_surface_format(DXGI_FORMAT format)
{
switch (format)
{
case DXGI_FORMAT_R32_FLOAT: return FFX_API_SURFACE_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R16G16B16A16_FLOAT: return FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT;
case DXGI_FORMAT_R32G32B32A32_FLOAT: return FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT;
case DXGI_FORMAT_R10G10B10A2_UNORM: return FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM;
case DXGI_FORMAT_R8G8B8A8_UNORM: return FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_R16_FLOAT: return FFX_API_SURFACE_FORMAT_R16_FLOAT;
case DXGI_FORMAT_R8_UNORM: return FFX_API_SURFACE_FORMAT_R8_UNORM;
default: return FFX_API_SURFACE_FORMAT_UNKNOWN;
}
}
+695
View File
@@ -0,0 +1,695 @@
// Standalone check that AMD's FSR Ray Regeneration runtime runs on this D3D12 device, separate from
// any game and from the NGX shim. It:
// 1. creates a D3D12 device and reports the adapter identity the runtime will see through DXGI,
// 2. loads the given amd_fidelityfx_denoiser_dx12.dll (unmodified, by path) and enumerates its
// denoiser providers against that device (FFX_API_QUERY_DESC_TYPE_GET_VERSIONS),
// 3. creates a context (this is where the runtime builds its shader pipelines) and asks it which
// provider it got,
// 4. dispatches a synthetic ray-traced scene with per-frame white noise: a static camera, then a
// moving one (with exact 2.5D motion vectors), then a camera cut with FFX_DENOISER_DISPATCH_RESET,
// 5. reads every output back and compares input and output against the noise-free signals, so a
// passthrough or a stale history cannot pass for denoising.
// Every stage prints one "stage=... result=..." line, and OUT_DIR/probe.json collects the lot.
//
// usage: fsr_rr_probe.exe DENOISER_DLL OUT_DIR [--size WxH] [--frames N] [--signals HEX] [--query-only]
// [--no-driver-extension]
// --no-driver-extension: do not load Proton's amdxc64.dll for an AMD GPU hidden as NVIDIA
// (see d4r_fsr_rr_identity.h); reproduces the "no provider" outcome
// --signals: FfxApiDenoiserSignalFlags of the radiance signals to run (default 0x36: direct and
// indirect diffuse and specular, AMD's four-signal configuration; 0x30 = indirect only)
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include "d4r_fsr_rr.h"
#include "windows/fsr_rr_identity.h"
#include "fsr_rr_scene.h"
namespace
{
using namespace fsr_scene;
FILE* g_json = nullptr;
bool g_jsonFirst = true;
void json_raw(const char* format, ...)
{
if (g_json == nullptr)
return;
va_list args;
va_start(args, format);
std::vfprintf(g_json, format, args);
va_end(args);
}
// One top-level "key": value pair; value is preformatted JSON.
void json_field(const char* key, const std::string& value)
{
json_raw("%s\n \"%s\": %s", g_jsonFirst ? "" : ",", key, value.c_str());
g_jsonFirst = false;
}
std::string quoted(const std::string& text)
{
std::string out = "\"";
for (char c : text)
{
if (c == '"' || c == '\\')
out += '\\';
if (static_cast<unsigned char>(c) < 0x20)
continue;
out += c;
}
return out + "\"";
}
std::string narrow(const wchar_t* text)
{
char buffer[1024] = {};
WideCharToMultiByte(CP_UTF8, 0, text, -1, buffer, sizeof(buffer), nullptr, nullptr);
return buffer;
}
std::string format(const char* pattern, ...)
{
char buffer[2048];
va_list args;
va_start(args, pattern);
std::vsnprintf(buffer, sizeof(buffer), pattern, args);
va_end(args);
return buffer;
}
void stage(const char* name, bool ok, const std::string& detail)
{
std::printf("stage=%s result=%s %s\n", name, ok ? "ok" : "FAIL", detail.c_str());
std::fflush(stdout);
json_field((std::string("stage_") + name).c_str(),
format("{\"ok\": %s, \"detail\": %s}", ok ? "true" : "false", quoted(detail).c_str()));
}
std::vector<std::string> g_messages;
void ffx_message(uint32_t type, const wchar_t* message)
{
const std::string text = narrow(message);
std::printf("ffx %s: %s\n", type == FFX_API_MESSAGE_TYPE_WARNING ? "warning" : "error", text.c_str());
std::fflush(stdout);
if (g_messages.size() < 64)
g_messages.push_back(text);
}
// --- D3D12 plumbing -------------------------------------------------------------------
#define CHECK_HR(expr) \
do \
{ \
const HRESULT hr_ = (expr); \
if (FAILED(hr_)) \
{ \
std::printf("%s failed: 0x%08lx (line %d)\n", #expr, static_cast<unsigned long>(hr_), __LINE__); \
return false; \
} \
} while (0)
struct Texture
{
ID3D12Resource* resource = nullptr;
D3D12_RESOURCE_DESC desc = {};
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
UINT rows = 0;
UINT64 rowBytes = 0, total = 0;
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON;
};
ID3D12Device* g_device = nullptr;
bool make_texture(Texture& texture, uint32_t width, uint32_t height, DXGI_FORMAT format, bool uav,
D3D12_RESOURCE_STATES state)
{
texture.desc = {};
texture.desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texture.desc.Width = width;
texture.desc.Height = height;
texture.desc.DepthOrArraySize = 1;
texture.desc.MipLevels = 1;
texture.desc.Format = format;
texture.desc.SampleDesc.Count = 1;
texture.desc.Flags = uav ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
CHECK_HR(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &texture.desc, state, nullptr,
IID_PPV_ARGS(&texture.resource)));
texture.state = state;
g_device->GetCopyableFootprints(&texture.desc, 0, 1, 0, &texture.footprint, &texture.rows, &texture.rowBytes,
&texture.total);
return true;
}
bool make_buffer(ID3D12Resource*& buffer, UINT64 bytes, D3D12_HEAP_TYPE type, uint8_t*& mapped)
{
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = bytes;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = type;
const D3D12_RESOURCE_STATES state =
type == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST;
CHECK_HR(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
IID_PPV_ARGS(&buffer)));
CHECK_HR(buffer->Map(0, nullptr, reinterpret_cast<void**>(&mapped)));
return true;
}
void barrier(ID3D12GraphicsCommandList* list, Texture& texture, D3D12_RESOURCE_STATES after)
{
if (texture.state == after)
return;
D3D12_RESOURCE_BARRIER b = {};
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition.pResource = texture.resource;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore = texture.state;
b.Transition.StateAfter = after;
list->ResourceBarrier(1, &b);
texture.state = after;
}
// Copies `texelBytes`-sized texels from `source` (tightly packed rows) into the upload buffer at
// `offset` with the texture's footprint, and records the copy.
void upload(ID3D12GraphicsCommandList* list, Texture& texture, ID3D12Resource* staging, uint8_t* mapped,
UINT64& offset, const void* source, size_t texelBytes)
{
offset = (offset + D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT - 1) & ~UINT64(D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT - 1);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = texture.footprint;
footprint.Offset = offset;
const size_t rowBytes = texelBytes * texture.desc.Width;
for (UINT row = 0; row < texture.rows; ++row)
std::memcpy(mapped + offset + row * footprint.Footprint.RowPitch,
static_cast<const uint8_t*>(source) + row * rowBytes, rowBytes);
offset += texture.total;
barrier(list, texture, D3D12_RESOURCE_STATE_COPY_DEST);
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = texture.resource;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = staging;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = footprint;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
void readback(ID3D12GraphicsCommandList* list, Texture& texture, ID3D12Resource* buffer, UINT64 offset)
{
barrier(list, texture, D3D12_RESOURCE_STATE_COPY_SOURCE);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = texture.footprint;
footprint.Offset = offset;
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = buffer;
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
dst.PlacedFootprint = footprint;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = texture.resource;
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
// --- metrics and captures -------------------------------------------------------------
struct Error
{
double input = 0, output = 0, outputVsPrevious = 0, change = 0;
size_t nonFinite = 0;
};
Error measure(const Frame& frame, const Frame* previous, const uint8_t* rows, UINT pitch, uint32_t width,
uint32_t height, int signal)
{
Error e;
size_t n = 0;
for (uint32_t y = 2; y + 2 < height; ++y)
for (uint32_t x = 2; x + 2 < width; ++x)
{
const size_t i = static_cast<size_t>(y) * width + x;
const uint16_t* out = reinterpret_cast<const uint16_t*>(rows + static_cast<size_t>(y) * pitch) + x * 4;
for (int c = 0; c < 3; ++c)
{
const float o = from_half(out[c]);
if (!std::isfinite(o))
{
++e.nonFinite;
continue;
}
if (!frame.valid[i])
continue;
const float clean = frame.clean[signal][i * 3 + c];
const float in = from_half(frame.noisy[signal][i * 4 + c]);
e.input += (in - clean) * (in - clean);
e.output += (o - clean) * (o - clean);
e.change += std::fabs(o - in);
if (previous != nullptr && previous->valid[i])
{
const float old = previous->clean[signal][i * 3 + c];
e.outputVsPrevious += (o - old) * (o - old);
}
++n;
}
}
if (n > 0)
{
e.input = std::sqrt(e.input / n);
e.output = std::sqrt(e.output / n);
e.outputVsPrevious = std::sqrt(e.outputVsPrevious / n);
e.change /= n;
}
return e;
}
// Tonemapped composite (diffuse signals x diffuse albedo + specular signals x specular albedo).
void write_ppm(const std::string& path, uint32_t width, uint32_t height, const std::vector<V3>& radiance)
{
FILE* file = std::fopen(path.c_str(), "wb");
if (file == nullptr)
return;
std::fprintf(file, "P6\n%u %u\n255\n", width, height);
std::vector<uint8_t> row(width * 3);
for (uint32_t y = 0; y < height; ++y)
{
for (uint32_t x = 0; x < width; ++x)
{
const V3 c = radiance[static_cast<size_t>(y) * width + x];
const float rgb[3] = {c.x, c.y, c.z};
for (int k = 0; k < 3; ++k)
{
const float mapped = std::pow(std::max(0.0f, rgb[k]) / (1.0f + std::max(0.0f, rgb[k])), 1.0f / 2.2f);
row[x * 3 + k] = static_cast<uint8_t>(std::lround(saturate(mapped) * 255.0f));
}
}
std::fwrite(row.data(), 1, row.size(), file);
}
std::fclose(file);
}
} // namespace
int wmain(int argc, wchar_t** argv)
{
if (argc < 3)
{
std::printf("usage: fsr_rr_probe.exe DENOISER_DLL OUT_DIR [--size WxH] [--frames N] [--signals HEX] [--query-only]\n");
return 2;
}
const std::wstring dllPath = argv[1];
const std::string outDir = narrow(argv[2]);
uint32_t width = 960, height = 540, frames = 48, signals = 0x36;
bool queryOnly = false, driverExtension = true;
for (int i = 3; i < argc; ++i)
{
const std::wstring arg = argv[i];
if (arg == L"--size" && i + 1 < argc)
std::swscanf(argv[++i], L"%ux%u", &width, &height);
else if (arg == L"--frames" && i + 1 < argc)
frames = static_cast<uint32_t>(std::wcstoul(argv[++i], nullptr, 0));
else if (arg == L"--signals" && i + 1 < argc)
signals = static_cast<uint32_t>(std::wcstoul(argv[++i], nullptr, 16));
else if (arg == L"--query-only")
queryOnly = true;
else if (arg == L"--no-driver-extension")
driverExtension = false;
}
signals &= 0x36;
if (signals == 0 || frames < 12)
{
std::printf("need at least one radiance signal and 12 frames\n");
return 2;
}
CreateDirectoryA(outDir.c_str(), nullptr);
g_json = std::fopen((outDir + "\\probe.json").c_str(), "w");
json_raw("{");
json_field("denoiser_dll", quoted(narrow(dllPath.c_str())));
json_field("render_size", format("[%u, %u]", width, height));
json_field("signal_flags", format("%u", signals));
json_field("frames", format("%u", frames));
int exitCode = 1;
auto finish = [&](int code) {
json_raw("\n}\n");
if (g_json)
std::fclose(g_json);
g_json = nullptr;
return code;
};
// 1. Device, and the adapter identity DXGI reports (what the runtime can see).
IDXGIFactory4* factory = nullptr;
IDXGIAdapter1* adapter = nullptr;
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory))) || FAILED(factory->EnumAdapters1(0, &adapter)))
{
stage("device", false, "no DXGI adapter");
return finish(1);
}
DXGI_ADAPTER_DESC1 adapterDesc = {};
adapter->GetDesc1(&adapterDesc);
if (FAILED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&g_device))))
{
stage("device", false, "D3D12CreateDevice failed");
return finish(1);
}
D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {D3D_SHADER_MODEL_6_6};
g_device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
stage("device", true,
format("adapter=\"%s\" vendor=0x%04x device=0x%04x luid=%08lx:%08lx shader_model=0x%x",
narrow(adapterDesc.Description).c_str(), adapterDesc.VendorId, adapterDesc.DeviceId,
static_cast<unsigned long>(adapterDesc.AdapterLuid.HighPart),
static_cast<unsigned long>(adapterDesc.AdapterLuid.LowPart),
static_cast<unsigned>(shaderModel.HighestShaderModel)));
json_field("adapter", format("{\"name\": %s, \"vendor\": %u, \"device\": %u}",
quoted(narrow(adapterDesc.Description)).c_str(), adapterDesc.VendorId,
adapterDesc.DeviceId));
// 2. Runtime and provider.
FsrRrRuntime runtime;
if (!runtime.load(dllPath))
{
stage("load", false, format("%s (error %lu)", fsr_rr_stage_name(runtime.stage), GetLastError()));
return finish(1);
}
stage("load", true, "mapped=" + narrow(runtime.mappedPath.c_str()));
if (driverExtension)
{
std::string detail;
const bool loaded = d4r_fsr_rr_driver_extension(g_device, detail);
stage("driver_extension", loaded, detail);
}
const ffxReturnCode_t debug = runtime.set_message_callback(&ffx_message, FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE);
std::printf("ffx global debug callback: %s\n", fsr_rr_return_name(debug));
const bool provider = runtime.query_providers(g_device);
{
std::string list = "[";
for (size_t i = 0; i < runtime.versionIds.size(); ++i)
list += format("%s{\"id\": \"0x%016llx\", \"name\": %s}", i ? ", " : "",
static_cast<unsigned long long>(runtime.versionIds[i]),
quoted(runtime.versionNames[i]).c_str());
json_field("providers", list + "]");
}
stage("provider", provider,
provider ? format("count=%zu selected=0x%016llx name=\"%s\"", runtime.versionIds.size(),
static_cast<unsigned long long>(runtime.providerId), runtime.providerName.c_str())
: format("%s (query %s)", fsr_rr_stage_name(runtime.stage), fsr_rr_return_name(runtime.queryResult)));
if (!provider || queryOnly)
return finish(provider ? 0 : 1);
FfxApiEffectMemoryUsage memory = {};
const ffxReturnCode_t memoryResult = fsr_rr_query_memory(runtime, g_device, width, height, signals, 0, memory);
json_field("gpu_memory", format("{\"result\": %u, \"total_mb\": %.2f, \"aliasable_mb\": %.2f}", memoryResult,
memory.totalUsageInBytes / 1048576.0, memory.aliasableUsageInBytes / 1048576.0));
std::printf("gpu memory query: %s total=%.2f MB aliasable=%.2f MB\n", fsr_rr_return_name(memoryResult),
memory.totalUsageInBytes / 1048576.0, memory.aliasableUsageInBytes / 1048576.0);
// 3. Context (shader pipelines are created here: the slow and the most likely to fail part).
ffxContext context = nullptr;
const auto createStart = std::chrono::steady_clock::now();
const ffxReturnCode_t created = fsr_rr_create_context(runtime, g_device, width, height, signals, 0, context);
const double createMs =
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - createStart).count();
json_field("context_create_ms", format("%.1f", createMs));
if (created != FFX_API_RETURN_OK)
{
stage("context", false, format("ffxCreateContext -> %s after %.1f ms", fsr_rr_return_name(created), createMs));
return finish(1);
}
uint64_t actualId = 0;
const std::string actual = fsr_rr_context_provider(runtime, context, &actualId);
stage("context", true, format("%.1f ms, provider in use 0x%016llx \"%s\"", createMs,
static_cast<unsigned long long>(actualId), actual.c_str()));
json_field("provider_in_use", quoted(actual));
// 4. Resources and frames.
ID3D12CommandQueue* queue = nullptr;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ID3D12CommandAllocator* allocator = nullptr;
ID3D12GraphicsCommandList* list = nullptr;
ID3D12Fence* fence = nullptr;
if (FAILED(g_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&queue))) ||
FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator))) ||
FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator, nullptr, IID_PPV_ARGS(&list))) ||
FAILED(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence))))
{
stage("dispatch", false, "command objects");
return finish(1);
}
list->Close();
HANDLE event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
uint64_t fenceValue = 0;
const D3D12_RESOURCE_STATES readState =
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
Texture depth, motion, normals, diffuseAlbedo, specularAlbedo, input[kSignalCount], output[kSignalCount];
bool ok = make_texture(depth, width, height, DXGI_FORMAT_R32_FLOAT, false, readState) &&
make_texture(motion, width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, false, readState) &&
make_texture(normals, width, height, DXGI_FORMAT_R10G10B10A2_UNORM, false, readState) &&
make_texture(diffuseAlbedo, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, false, readState) &&
make_texture(specularAlbedo, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, false, readState);
for (int s = 0; s < kSignalCount && ok; ++s)
ok = make_texture(input[s], width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, false, readState) &&
make_texture(output[s], width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, true,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
ID3D12Resource *staging = nullptr, *readbackBuffer = nullptr, *timestampBuffer = nullptr;
uint8_t *stagingMapped = nullptr, *readbackMapped = nullptr, *timestampMapped = nullptr;
const UINT64 planeBytes = output[0].total;
ok = ok && make_buffer(staging, 16 * (planeBytes + 65536), D3D12_HEAP_TYPE_UPLOAD, stagingMapped) &&
make_buffer(readbackBuffer, kSignalCount * planeBytes, D3D12_HEAP_TYPE_READBACK, readbackMapped) &&
make_buffer(timestampBuffer, 16, D3D12_HEAP_TYPE_READBACK, timestampMapped);
ID3D12QueryHeap* timestamps = nullptr;
D3D12_QUERY_HEAP_DESC queryDesc = {};
queryDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
queryDesc.Count = 2;
ok = ok && SUCCEEDED(g_device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&timestamps)));
UINT64 ticksPerSecond = 0;
queue->GetTimestampFrequency(&ticksPerSecond);
if (!ok)
{
stage("dispatch", false, "resource creation");
return finish(1);
}
const float aspect = static_cast<float>(width) / height;
const M4 projection = projection_matrix(1.0f, aspect, 0.1f, 1000.0f);
const uint32_t cutFrame = frames * 2 / 3, moveStart = frames / 3;
auto camera_at = [&](uint32_t f) -> Camera {
if (f >= cutFrame)
return {{3.5f, 1.6f, 1.5f}, {-0.5f, -0.6f, 7.5f}};
const float t = f >= moveStart ? static_cast<float>(f - moveStart) : 0.0f;
return {{-0.6f + 0.04f * t, 0.6f, -1.5f}, {0.0f + 0.02f * t, -0.1f, 6.5f}};
};
Frame frameData, previousFrame;
std::vector<double> gpuMs, cpuMs;
std::string frameLog = "[";
bool dispatchOk = true, converged = true, resetOk = true, finite = true, notPassthrough = true;
Camera previousCamera = camera_at(0);
for (uint32_t f = 0; f < frames; ++f)
{
const Camera camera = camera_at(f);
const bool reset = f == 0 || f == cutFrame;
std::swap(previousFrame, frameData);
render(frameData, width, height, camera, reset ? camera : previousCamera, projection, f);
allocator->Reset();
list->Reset(allocator, nullptr);
UINT64 offset = 0;
upload(list, depth, staging, stagingMapped, offset, frameData.depth.data(), 4);
upload(list, motion, staging, stagingMapped, offset, frameData.motion.data(), 8);
upload(list, normals, staging, stagingMapped, offset, frameData.normals.data(), 4);
upload(list, diffuseAlbedo, staging, stagingMapped, offset, frameData.diffuseAlbedo.data(), 4);
upload(list, specularAlbedo, staging, stagingMapped, offset, frameData.specularAlbedo.data(), 4);
for (int s = 0; s < kSignalCount; ++s)
if (signals & kSignalFlags[s])
upload(list, input[s], staging, stagingMapped, offset, frameData.noisy[s].data(), 8);
Texture* all[] = {&depth, &motion, &normals, &diffuseAlbedo, &specularAlbedo};
for (Texture* t : all)
barrier(list, *t, readState);
for (int s = 0; s < kSignalCount; ++s)
{
barrier(list, input[s], readState);
barrier(list, output[s], D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
ffxDispatchDescDenoiser desc = {};
desc.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER;
desc.commandList = list;
const uint32_t readFfx = FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ;
desc.linearDepth = fsr_rr_texture(depth.resource, depth.desc, FFX_API_SURFACE_FORMAT_R32_FLOAT, readFfx);
desc.motionVectors = fsr_rr_texture(motion.resource, motion.desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, readFfx);
desc.normals = fsr_rr_texture(normals.resource, normals.desc, FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, readFfx);
desc.specularAlbedo = fsr_rr_texture(specularAlbedo.resource, specularAlbedo.desc, FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, readFfx);
desc.diffuseAlbedo = fsr_rr_texture(diffuseAlbedo.resource, diffuseAlbedo.desc, FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, readFfx);
desc.motionVectorScale = {1.0f, 1.0f, 1.0f};
desc.jitterOffsets = {0.0f, 0.0f};
const V3 delta = previousCamera.eye - camera.eye;
desc.cameraPositionDelta = reset ? FfxApiFloatCoords3D{0, 0, 0} : FfxApiFloatCoords3D{delta.x, delta.y, delta.z};
const M4 view = view_matrix(camera.eye, camera.target);
std::memcpy(&desc.view, &view, sizeof(desc.view));
std::memcpy(&desc.projection, &projection, sizeof(desc.projection));
desc.linearDepthBounds = {0.0f, 1000.0f};
desc.renderSize = {width, height};
desc.frameIndex = f;
desc.flags = reset ? FFX_DENOISER_DISPATCH_RESET : 0;
ffxDispatchDescDenoiserDirectDiffuse dd = {};
ffxDispatchDescDenoiserDirectSpecular ds = {};
ffxDispatchDescDenoiserIndirectDiffuse id = {};
ffxDispatchDescDenoiserIndirectSpecular is = {};
dd.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE;
ds.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_SPECULAR;
id.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE;
is.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR;
FfxApiDenoiserSignal* signalDescs[kSignalCount] = {&dd.signal, &ds.signal, &id.signal, &is.signal};
ffxApiHeader* headers[kSignalCount] = {&dd.header, &ds.header, &id.header, &is.header};
ffxApiHeader* tail = &desc.header;
for (int s = 0; s < kSignalCount; ++s)
{
if (!(signals & kSignalFlags[s]))
continue;
signalDescs[s]->input = fsr_rr_texture(input[s].resource, input[s].desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, readFfx);
signalDescs[s]->output = fsr_rr_texture(output[s].resource, output[s].desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT,
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
tail->pNext = headers[s];
tail = headers[s];
}
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0);
const auto cpuStart = std::chrono::steady_clock::now();
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
cpuMs.push_back(std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpuStart).count());
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 1);
list->ResolveQueryData(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0, 2, timestampBuffer, 0);
if (dispatched != FFX_API_RETURN_OK)
{
stage("dispatch", false, format("frame %u: ffxDispatch -> %s", f, fsr_rr_return_name(dispatched)));
dispatchOk = false;
list->Close();
break;
}
for (int s = 0; s < kSignalCount; ++s)
if (signals & kSignalFlags[s])
readback(list, output[s], readbackBuffer, s * planeBytes);
if (FAILED(list->Close()))
{
stage("dispatch", false, format("frame %u: Close failed", f));
dispatchOk = false;
break;
}
ID3D12CommandList* lists[] = {list};
queue->ExecuteCommandLists(1, lists);
queue->Signal(fence, ++fenceValue);
fence->SetEventOnCompletion(fenceValue, event);
if (WaitForSingleObject(event, 20000) != WAIT_OBJECT_0)
{
stage("dispatch", false, format("frame %u: GPU did not finish within 20 s", f));
dispatchOk = false;
break;
}
const HRESULT removed = g_device->GetDeviceRemovedReason();
if (FAILED(removed))
{
stage("dispatch", false, format("frame %u: device removed 0x%08lx", f, static_cast<unsigned long>(removed)));
dispatchOk = false;
break;
}
uint64_t stamps[2];
std::memcpy(stamps, timestampMapped, sizeof(stamps));
const double gpu = ticksPerSecond ? (stamps[1] - stamps[0]) * 1000.0 / ticksPerSecond : -1.0;
gpuMs.push_back(gpu);
// 5. Compare against the clean signals.
frameLog += format("%s\n {\"frame\": %u, \"reset\": %s, \"gpu_ms\": %.3f, \"cpu_ms\": %.3f, \"signals\": {",
f ? "," : "", f, reset ? "true" : "false", gpu, cpuMs.back());
bool firstSignal = true;
std::vector<V3> composeOut(static_cast<size_t>(width) * height), composeIn(composeOut.size()),
composeClean(composeOut.size());
for (int s = 0; s < kSignalCount; ++s)
{
if (!(signals & kSignalFlags[s]))
continue;
const uint8_t* rows = readbackMapped + s * planeBytes;
const Error e = measure(frameData, f == cutFrame ? &previousFrame : nullptr, rows,
output[s].footprint.Footprint.RowPitch, width, height, s);
frameLog += format("%s\"%s\": {\"rmse_in\": %.5f, \"rmse_out\": %.5f, \"mean_abs_change\": %.5f, "
"\"rmse_out_vs_previous_scene\": %.5f, \"nonfinite\": %zu}",
firstSignal ? "" : ", ", kSignalNames[s], e.input, e.output, e.change,
e.outputVsPrevious, e.nonFinite);
firstSignal = false;
finite = finite && e.nonFinite == 0;
notPassthrough = notPassthrough && (f < 2 || e.change > 1e-3 * (e.input + 1e-6));
// Converged before the cut: at least 8 static or slowly moving frames into history.
if (f + 1 == cutFrame && !(e.output < 0.5 * e.input))
converged = false;
if (f == cutFrame && !(e.output < e.outputVsPrevious))
resetOk = false;
if (f + 1 == frames && !(e.output < 0.6 * e.input))
resetOk = false;
const bool diffuse = s == kDirectDiffuse || s == kIndirectDiffuse;
for (uint32_t y = 0; y < height; ++y)
for (uint32_t x = 0; x < width; ++x)
{
const size_t i = static_cast<size_t>(y) * width + x;
const V3 albedo = diffuse ? frameData.diffuseLinear[i] : frameData.specularLinear[i];
const uint16_t* o = reinterpret_cast<const uint16_t*>(rows + static_cast<size_t>(y) * output[s].footprint.Footprint.RowPitch) + x * 4;
composeOut[i] = composeOut[i] + mul(V3{from_half(o[0]), from_half(o[1]), from_half(o[2])}, albedo);
composeIn[i] = composeIn[i] + mul(V3{from_half(frameData.noisy[s][i * 4]), from_half(frameData.noisy[s][i * 4 + 1]),
from_half(frameData.noisy[s][i * 4 + 2])}, albedo);
composeClean[i] = composeClean[i] + mul(V3{frameData.clean[s][i * 3], frameData.clean[s][i * 3 + 1],
frameData.clean[s][i * 3 + 2]}, albedo);
}
}
frameLog += "}}";
if (f == 0 || f + 1 == moveStart || f + 1 == cutFrame || f == cutFrame || f == cutFrame + 1 || f + 1 == frames)
{
write_ppm(format("%s\\frame%03u_noisy.ppm", outDir.c_str(), f), width, height, composeIn);
write_ppm(format("%s\\frame%03u_denoised.ppm", outDir.c_str(), f), width, height, composeOut);
write_ppm(format("%s\\frame%03u_clean.ppm", outDir.c_str(), f), width, height, composeClean);
}
previousCamera = camera;
}
json_field("frame_log", frameLog + "\n ]");
if (dispatchOk)
{
std::vector<double> warm(gpuMs.begin() + std::min<size_t>(gpuMs.size(), 3), gpuMs.end());
std::sort(warm.begin(), warm.end());
const double median = warm.empty() ? -1 : warm[warm.size() / 2];
std::vector<double> cpuWarm(cpuMs.begin() + std::min<size_t>(cpuMs.size(), 3), cpuMs.end());
std::sort(cpuWarm.begin(), cpuWarm.end());
stage("dispatch", true, format("%u frames, GPU median %.3f ms (first frame %.3f ms), CPU record median %.3f ms",
frames, median, gpuMs.empty() ? -1.0 : gpuMs[0],
cpuWarm.empty() ? -1.0 : cpuWarm[cpuWarm.size() / 2]));
json_field("gpu_ms_median", format("%.4f", median));
stage("output_finite", finite, finite ? "no NaN/Inf in any output" : "non-finite output values");
stage("not_passthrough", notPassthrough, "outputs differ from the noisy inputs");
stage("denoised", converged, format("before the cut (frame %u) every signal's RMSE vs clean is < 0.5x the input's",
cutFrame - 1));
stage("reset", resetOk, format("frame %u (reset) is closer to the new scene than the old one; final frame < 0.6x input",
cutFrame));
exitCode = finite && notPassthrough && converged && resetOk ? 0 : 1;
}
std::string messages = "[";
for (size_t i = 0; i < g_messages.size(); ++i)
messages += (i ? ", " : "") + quoted(g_messages[i]);
json_field("ffx_messages", messages + "]");
runtime.destroyContext(&context, nullptr);
return finish(exitCode);
}
+515
View File
@@ -0,0 +1,515 @@
// The synthetic ray-traced scene shared by tools/fsr_rr_probe.cpp (AMD's denoiser alone) and
// tools/rr_backend_check.cpp (a game's NGX Ray Reconstruction request through the shim): a camera over
// a checkered floor, three spheres and a wall, with smooth noise-free lighting signals and one-sample
// white-noise versions of them, every guide the denoisers read, and exact 2.5D motion. Because the
// noise-free signals are known, outputs are compared against ground truth rather than another
// denoiser.
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <vector>
namespace fsr_scene
{
// --- small math -----------------------------------------------------------------
struct V3
{
float x, y, z;
};
inline V3 operator+(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
inline V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
inline V3 operator*(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
inline V3 mul(V3 a, V3 b) { return {a.x * b.x, a.y * b.y, a.z * b.z}; }
inline float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
inline V3 cross(V3 a, V3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
inline V3 normalize(V3 a) { return a * (1.0f / std::sqrt(dot(a, a))); }
inline float saturate(float v) { return std::min(1.0f, std::max(0.0f, v)); }
inline uint16_t to_half(float value)
{
uint32_t bits;
std::memcpy(&bits, &value, 4);
const uint32_t sign = (bits >> 16) & 0x8000u;
int exponent = static_cast<int>((bits >> 23) & 0xff) - 127 + 15;
uint32_t mantissa = bits & 0x7fffffu;
if (((bits >> 23) & 0xff) == 0xff)
return static_cast<uint16_t>(sign | 0x7c00u | (mantissa ? 0x200u : 0u));
if (exponent >= 31)
return static_cast<uint16_t>(sign | 0x7bffu); // clamp to the largest finite half
if (exponent <= 0)
{
if (exponent < -10)
return static_cast<uint16_t>(sign);
mantissa |= 0x800000u;
const uint32_t shift = static_cast<uint32_t>(14 - exponent);
uint32_t half = mantissa >> shift;
if ((mantissa >> (shift - 1)) & 1u)
++half;
return static_cast<uint16_t>(sign | half);
}
uint32_t half = sign | (static_cast<uint32_t>(exponent) << 10) | (mantissa >> 13);
if (mantissa & 0x1000u)
++half;
return static_cast<uint16_t>(half);
}
inline float from_half(uint16_t half)
{
const uint32_t sign = (half & 0x8000u) << 16;
uint32_t exponent = (half >> 10) & 0x1fu;
uint32_t mantissa = half & 0x3ffu;
uint32_t bits;
if (exponent == 0)
{
if (mantissa == 0)
bits = sign;
else
{
exponent = 127 - 15 + 1;
while ((mantissa & 0x400u) == 0)
{
mantissa <<= 1;
--exponent;
}
bits = sign | (exponent << 23) | ((mantissa & 0x3ffu) << 13);
}
}
else if (exponent == 31)
bits = sign | 0x7f800000u | (mantissa << 13);
else
bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13);
float value;
std::memcpy(&value, &bits, 4);
return value;
}
// Row-major, row-vector (v' = vM) 4x4, the convention ffx_denoiser.h asks for.
struct M4
{
float m[4][4];
};
inline M4 view_matrix(V3 eye, V3 target)
{
const V3 forward = normalize(target - eye);
const V3 right = normalize(cross({0, 1, 0}, forward));
const V3 up = cross(forward, right);
M4 v = {};
v.m[0][0] = right.x, v.m[0][1] = up.x, v.m[0][2] = forward.x;
v.m[1][0] = right.y, v.m[1][1] = up.y, v.m[1][2] = forward.y;
v.m[2][0] = right.z, v.m[2][1] = up.z, v.m[2][2] = forward.z;
v.m[3][0] = -dot(eye, right), v.m[3][1] = -dot(eye, up), v.m[3][2] = -dot(eye, forward), v.m[3][3] = 1;
return v;
}
inline M4 projection_matrix(float fovY, float aspect, float zNear, float zFar)
{
const float ys = 1.0f / std::tan(fovY * 0.5f);
M4 p = {};
p.m[0][0] = ys / aspect;
p.m[1][1] = ys;
p.m[2][2] = zFar / (zFar - zNear);
p.m[2][3] = 1;
p.m[3][2] = -zNear * zFar / (zFar - zNear);
return p;
}
inline void transform(const M4& m, V3 v, float out[4])
{
for (int c = 0; c < 4; ++c)
out[c] = v.x * m.m[0][c] + v.y * m.m[1][c] + v.z * m.m[2][c] + m.m[3][c];
}
// --- the synthetic scene ----------------------------------------------------------
struct Camera
{
V3 eye, target;
};
struct Sphere
{
V3 center;
float radius;
V3 base;
float roughness, metal;
};
inline const Sphere kSpheres[] = {
{{0.0f, 0.2f, 6.0f}, 1.2f, {0.8f, 0.3f, 0.2f}, 0.35f, 0.0f},
{{2.4f, -0.3f, 7.5f}, 0.7f, {0.95f, 0.9f, 0.8f}, 0.15f, 1.0f},
{{-2.2f, -0.4f, 5.0f}, 0.6f, {0.2f, 0.5f, 0.85f}, 0.6f, 0.0f},
};
inline constexpr float kGroundY = -1.0f;
inline constexpr float kWallZ = 14.0f;
// Optional near-mirror pond in the floor (rr_backend_check --water): the reflection is the traced scene, with
// guides as Alan Wake 2 hands them for its water (flat diffuse albedo 0.5, specular albedo ~0.96, roughness ~0.047).
inline bool g_water = false;
inline constexpr float kWaterRoughness = 0.047f;
inline constexpr float kWaterMinX = -4.0f, kWaterMaxX = 4.5f, kWaterMinZ = 0.5f, kWaterMaxZ = 4.8f;
inline const V3 kLight = normalize({0.45f, 0.9f, -0.35f});
struct Hit
{
float t = 1e30f;
V3 position{}, normal{}, base{};
float roughness = 1, metal = 0;
bool valid = false, water = false;
};
inline bool hit_sphere(V3 origin, V3 direction, const Sphere& s, float& t)
{
const V3 oc = origin - s.center;
const float b = dot(oc, direction);
const float c = dot(oc, oc) - s.radius * s.radius;
const float h = b * b - c;
if (h < 0)
return false;
const float root = -b - std::sqrt(h);
if (root <= 1e-3f)
return false;
t = root;
return true;
}
inline Hit trace(V3 origin, V3 direction)
{
Hit hit;
float t;
for (const Sphere& s : kSpheres)
if (hit_sphere(origin, direction, s, t) && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = normalize(hit.position - s.center);
hit.base = s.base;
hit.roughness = s.roughness;
hit.metal = s.metal;
hit.valid = true;
}
if (direction.y < -1e-4f)
{
t = (kGroundY - origin.y) / direction.y;
if (t > 1e-3f && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = {0, 1, 0};
const int checker = (static_cast<int>(std::floor(hit.position.x)) + static_cast<int>(std::floor(hit.position.z))) & 1;
hit.base = checker ? V3{0.75f, 0.75f, 0.72f} : V3{0.22f, 0.24f, 0.25f};
hit.roughness = 0.55f;
hit.metal = 0;
hit.valid = true;
const V3 q = hit.position;
if (g_water && q.x > kWaterMinX && q.x < kWaterMaxX && q.z > kWaterMinZ && q.z < kWaterMaxZ)
{
// small world-space ripples, so the reflection has fine detail a denoiser can lose
hit.normal = normalize({0.035f * std::sin(5.1f * q.x + 1.3f * q.z) + 0.02f * std::sin(11.7f * q.z),
1.0f,
0.035f * std::cos(4.3f * q.z - 0.7f * q.x) + 0.02f * std::cos(13.1f * q.x)});
hit.base = {0.5f, 0.5f, 0.5f};
hit.roughness = kWaterRoughness;
hit.water = true;
}
}
}
if (direction.z > 1e-4f)
{
t = (kWallZ - origin.z) / direction.z;
if (t > 1e-3f && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = {0, 0, -1};
hit.base = {0.55f, 0.5f, 0.45f};
hit.roughness = 0.85f;
hit.metal = 0;
hit.valid = true;
}
}
return hit;
}
inline bool shadowed(V3 position, V3 normal)
{
float t;
const V3 origin = position + normal * 2e-3f;
for (const Sphere& s : kSpheres)
if (hit_sphere(origin, kLight, s, t))
return true;
return false;
}
// AMD's ApproximateBRDF (denoising.md 5.5.2, after Ray Tracing Gems ch. 32).
inline V3 approximate_brdf(V3 f0, float alpha, float nov)
{
nov = std::fabs(nov);
const float x[4] = {1, nov, nov * nov, nov * nov * nov};
const float y[4] = {1, alpha, alpha * alpha, alpha * alpha * alpha};
const float m1[2][2] = {{0.99044f, -1.28514f}, {1.29678f, -0.755907f}};
const float m2[3][3] = {{1.0f, 2.92338f, 59.4188f}, {20.3225f, -27.0302f, 222.592f}, {121.563f, 626.13f, 316.627f}};
const float m3[2][2] = {{0.0365463f, 3.32707f}, {9.0632f, -9.04756f}};
const float m4[3][3] = {{1.0f, 3.59685f, -1.36772f}, {9.04401f, -16.3174f, 9.22949f}, {5.56589f, 19.7886f, -20.2123f}};
// HLSL mul(M, v) with M row-major: (M v)_i = sum_j M[i][j] v_j
const float a0 = m1[0][0] * x[0] + m1[0][1] * x[1], a1 = m1[1][0] * x[0] + m1[1][1] * x[1];
const float xw[3] = {x[0], x[1], x[3]}, yw[3] = {y[0], y[1], y[3]};
float b[3];
for (int i = 0; i < 3; ++i)
b[i] = m2[i][0] * xw[0] + m2[i][1] * xw[1] + m2[i][2] * xw[2];
const float c0 = m3[0][0] * x[0] + m3[0][1] * x[1], c1 = m3[1][0] * x[0] + m3[1][1] * x[1];
const float xz[3] = {x[0], x[2], x[3]};
float d[3];
for (int i = 0; i < 3; ++i)
d[i] = m4[i][0] * xz[0] + m4[i][1] * xz[1] + m4[i][2] * xz[2];
float bias = (a0 * y[0] + a1 * y[1]) / (b[0] * yw[0] + b[1] * yw[1] + b[2] * yw[2]);
const float scale = (c0 * y[0] + c1 * y[1]) / (d[0] * yw[0] + d[1] * yw[1] + d[2] * yw[2]);
bias *= saturate(f0.y * 50);
const float s = std::max(0.0f, scale), bb = std::max(0.0f, bias);
return {f0.x * s + bb, f0.y * s + bb, f0.z * s + bb};
}
inline V3 reflect_dir(V3 direction, V3 normal) { return direction - normal * (2 * dot(direction, normal)); }
inline V3 environment(V3 direction)
{
const float up = saturate(direction.y * 0.5f + 0.5f);
return V3{0.35f, 0.45f, 0.6f} * (0.4f + 1.2f * up);
}
inline uint32_t hash32(uint32_t x)
{
// lowbias32, as AMD's guide recommends for white noise
x ^= x >> 16;
x *= 0x7feb352du;
x ^= x >> 15;
x *= 0x846ca68bu;
x ^= x >> 16;
return x;
}
inline float uniform(uint32_t pixel, uint32_t frame, uint32_t stream)
{
return (hash32(pixel * 0x9e3779b9u ^ hash32(frame * 4u + stream + 0x1234567u)) >> 8) * (1.0f / 16777216.0f);
}
enum Signal
{
kDirectDiffuse,
kDirectSpecular,
kIndirectDiffuse,
kIndirectSpecular,
kSignalCount
};
// FfxApiDenoiserSignalFlags of each signal (DIRECT_DIFFUSE 2, DIRECT_SPECULAR 4, INDIRECT_DIFFUSE 16,
// INDIRECT_SPECULAR 32)
inline const uint32_t kSignalFlags[kSignalCount] = {1u << 1, 1u << 2, 1u << 4, 1u << 5};
inline const char* const kSignalNames[kSignalCount] = {"direct_diffuse", "direct_specular", "indirect_diffuse",
"indirect_specular"};
struct Frame
{
// FSR Ray Regeneration's own inputs (fsr_rr_probe)
std::vector<float> depth; // signed linear (view z)
std::vector<uint16_t> motion; // RGBA16F: prevUV - curUV, prev z - cur z
std::vector<uint32_t> normals; // R10G10B10A2: oct normal, roughness, material 0
std::vector<uint32_t> diffuseAlbedo, specularAlbedo; // RGBA8, sqrt encoded
std::vector<float> clean[kSignalCount]; // RGB
std::vector<uint16_t> noisy[kSignalCount]; // RGBA16F
std::vector<uint8_t> valid;
std::vector<V3> diffuseLinear, specularLinear;
// What a DLSS Ray Reconstruction game hands NGX (rr_backend_check)
std::vector<uint16_t> color; // RGBA16F: the noisy signals remodulated and summed, sky added
std::vector<V3> colorClean; // the same from the noise-free signals: the ground truth
std::vector<float> hardwareDepth; // D32: NDC z of the projection (1 for the sky)
std::vector<uint16_t> motionPixels; // RG16F: (previous - current) position in render pixels
std::vector<uint16_t> normalsRoughness; // RGBA16F: signed world normal, w = linear roughness
std::vector<uint32_t> diffuseAlbedoLinear, specularAlbedoLinear; // RGBA8 UNORM, linear
};
inline uint32_t pack_unorm8(V3 v)
{
auto q = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 255.0f)); };
return q(v.x) | (q(v.y) << 8) | (q(v.z) << 16) | (255u << 24);
}
inline uint32_t pack_normal(V3 n, float roughness)
{
// AMD's NormalToOctahedronUv (denoising.md 5.3.1)
const float l1 = std::fabs(n.x) + std::fabs(n.y) + std::fabs(n.z);
float x = n.x / l1, y = n.y / l1;
if (n.z < 0)
{
const float ox = (1.0f - std::fabs(y)) * (x >= 0 ? 1.0f : -1.0f);
const float oy = (1.0f - std::fabs(x)) * (y >= 0 ? 1.0f : -1.0f);
x = ox, y = oy;
}
auto q10 = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 1023.0f)); };
return q10(x * 0.5f + 0.5f) | (q10(y * 0.5f + 0.5f) << 10) | (q10(roughness) << 20) | (0u << 30);
}
// Renders one frame. (jitterX, jitterY): the sample offset from each pixel's centre in pixels (DLSS's
// Jitter.Offset is its negation). Motion is that of the sampled surface point.
inline void render(Frame& frame, uint32_t width, uint32_t height, const Camera& camera, const Camera& previous,
const M4& projection, uint32_t frameIndex, float jitterX = 0.0f, float jitterY = 0.0f)
{
const size_t count = static_cast<size_t>(width) * height;
frame.depth.assign(count, 0);
frame.motion.assign(count * 4, 0);
frame.normals.assign(count, 0);
frame.diffuseAlbedo.assign(count, 0);
frame.specularAlbedo.assign(count, 0);
frame.valid.assign(count, 0);
frame.diffuseLinear.assign(count, V3{});
frame.specularLinear.assign(count, V3{});
frame.color.assign(count * 4, 0);
frame.colorClean.assign(count, V3{});
frame.hardwareDepth.assign(count, 1.0f);
frame.motionPixels.assign(count * 2, 0);
frame.normalsRoughness.assign(count * 4, 0);
frame.diffuseAlbedoLinear.assign(count, 0);
frame.specularAlbedoLinear.assign(count, 0);
for (int s = 0; s < kSignalCount; ++s)
{
frame.clean[s].assign(count * 3, 0);
frame.noisy[s].assign(count * 4, 0);
}
const M4 view = view_matrix(camera.eye, camera.target);
const M4 previousView = view_matrix(previous.eye, previous.target);
const V3 forward = normalize(camera.target - camera.eye);
const V3 right = normalize(cross({0, 1, 0}, forward));
const V3 up = cross(forward, right);
const float tanX = 1.0f / projection.m[0][0], tanY = 1.0f / projection.m[1][1];
for (uint32_t py = 0; py < height; ++py)
for (uint32_t px = 0; px < width; ++px)
{
const size_t i = static_cast<size_t>(py) * width + px;
const float u = (px + 0.5f + jitterX) / width, v = (py + 0.5f + jitterY) / height;
const float ndcX = u * 2 - 1, ndcY = 1 - v * 2;
const V3 direction = normalize(forward + right * (ndcX * tanX) + up * (ndcY * tanY));
const Hit hit = trace(camera.eye, direction);
const uint32_t pixelId = static_cast<uint32_t>(i);
frame.color[i * 4 + 3] = to_half(1.0f);
frame.normalsRoughness[i * 4 + 2] = to_half(-1.0f);
frame.normalsRoughness[i * 4 + 3] = to_half(1.0f);
if (!hit.valid)
{
// Sky: outside linearDepthBounds, so the runtime passes it through.
frame.depth[i] = 5000.0f;
frame.normals[i] = pack_normal({0, 0, -1}, 1);
const V3 sky = environment(direction);
frame.colorClean[i] = sky;
frame.color[i * 4 + 0] = to_half(sky.x);
frame.color[i * 4 + 1] = to_half(sky.y);
frame.color[i * 4 + 2] = to_half(sky.z);
continue;
}
frame.valid[i] = 1;
float clip[4];
transform(view, hit.position, clip);
frame.depth[i] = clip[2];
float projected[4];
transform(projection, {clip[0], clip[1], clip[2]}, projected);
frame.hardwareDepth[i] = projected[2] / projected[3];
float prevView[4], prevClip[4];
transform(previousView, hit.position, prevView);
const M4& p = projection;
const V3 pv = {prevView[0], prevView[1], prevView[2]};
transform(p, pv, prevClip);
const float prevU = (prevClip[0] / prevClip[3]) * 0.5f + 0.5f;
const float prevV = 0.5f - (prevClip[1] / prevClip[3]) * 0.5f;
frame.motion[i * 4 + 0] = to_half(prevU - u);
frame.motion[i * 4 + 1] = to_half(prevV - v);
frame.motion[i * 4 + 2] = to_half(prevView[2] - clip[2]);
frame.motion[i * 4 + 3] = to_half(0);
frame.motionPixels[i * 2 + 0] = to_half((prevU - u) * width);
frame.motionPixels[i * 2 + 1] = to_half((prevV - v) * height);
const V3 diffuse = hit.base * (1.0f - hit.metal);
const V3 f0 = V3{0.04f, 0.04f, 0.04f} * (1.0f - hit.metal) + hit.base * hit.metal;
const V3 toEye = normalize(camera.eye - hit.position);
const float nov = std::max(dot(hit.normal, toEye), 1e-3f);
const V3 specular = hit.water ? V3{0.96f, 0.96f, 0.96f} : approximate_brdf(f0, hit.roughness * hit.roughness, nov);
frame.diffuseLinear[i] = diffuse;
frame.specularLinear[i] = specular;
frame.diffuseAlbedo[i] = pack_unorm8({std::sqrt(diffuse.x), std::sqrt(diffuse.y), std::sqrt(diffuse.z)});
frame.specularAlbedo[i] = pack_unorm8({std::sqrt(specular.x), std::sqrt(specular.y), std::sqrt(specular.z)});
frame.diffuseAlbedoLinear[i] = pack_unorm8(diffuse);
frame.specularAlbedoLinear[i] = pack_unorm8(specular);
frame.normals[i] = pack_normal(hit.normal, hit.roughness);
frame.normalsRoughness[i * 4 + 0] = to_half(hit.normal.x);
frame.normalsRoughness[i * 4 + 1] = to_half(hit.normal.y);
frame.normalsRoughness[i * 4 + 2] = to_half(hit.normal.z);
frame.normalsRoughness[i * 4 + 3] = to_half(hit.roughness);
// Demodulated (albedo-free) clean signals, smooth in world space.
const float visibility = shadowed(hit.position, hit.normal) ? 0.0f : 1.0f;
const float ndl = std::max(dot(hit.normal, kLight), 0.0f);
const V3 reflected = direction - hit.normal * (2 * dot(direction, hit.normal));
const float rdl = std::max(dot(reflected, kLight), 0.0f);
const float glossExponent = 2.0f / (hit.roughness * hit.roughness * hit.roughness * hit.roughness + 0.02f);
V3 clean[kSignalCount];
clean[kDirectDiffuse] = V3{1.0f, 0.95f, 0.85f} * (2.5f * ndl * visibility);
clean[kDirectSpecular] = V3{1.0f, 0.95f, 0.85f} * std::min(8.0f, 3.0f * std::pow(rdl, glossExponent) * visibility * (ndl > 0));
const float wobble = 0.12f * std::sin(1.7f * hit.position.x) * std::sin(1.3f * hit.position.z);
clean[kIndirectDiffuse] = environment(hit.normal) * (0.6f + wobble);
clean[kIndirectSpecular] = environment(reflected) * (0.8f + 0.2f * (1 - hit.roughness));
if (hit.water)
{
// The pond's light is its reflection: the traced scene, lit noise-free. Its diffuse lobe is
// nearly dark despite the 0.5 guide albedo.
clean[kDirectDiffuse] = clean[kDirectDiffuse] * 0.01f;
clean[kIndirectDiffuse] = clean[kIndirectDiffuse] * 0.01f;
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
if (bounce.valid && !bounce.water)
{
const float bounceVisibility = shadowed(bounce.position, bounce.normal) ? 0.0f : 1.0f;
const float bounceNdl = std::max(dot(bounce.normal, kLight), 0.0f);
const V3 light = V3{1.0f, 0.95f, 0.85f} * (2.5f * bounceNdl * bounceVisibility) +
environment(bounce.normal) * 0.6f;
clean[kIndirectSpecular] = mul(bounce.base * (1.0f - bounce.metal), light) +
environment(reflect_dir(reflected, bounce.normal)) * (0.04f + 0.9f * bounce.metal);
}
else
clean[kIndirectSpecular] = environment(reflected);
}
V3 combined{}, combinedClean{};
for (int s = 0; s < kSignalCount; ++s)
{
// One-sample-per-pixel white noise: a mean-one exponential weight shared by RGB,
// like a single path sample's throughput.
float sample = -std::log(std::max(1e-6f, 1.0f - uniform(pixelId, frameIndex, s)));
if (hit.water && s == kIndirectSpecular)
sample = 1.0f + 0.35f * (sample - 1.0f); // a mirror's reflection ray is far less noisy
frame.clean[s][i * 3 + 0] = clean[s].x;
frame.clean[s][i * 3 + 1] = clean[s].y;
frame.clean[s][i * 3 + 2] = clean[s].z;
frame.noisy[s][i * 4 + 0] = to_half(clean[s].x * sample);
frame.noisy[s][i * 4 + 1] = to_half(clean[s].y * sample);
frame.noisy[s][i * 4 + 2] = to_half(clean[s].z * sample);
// A: ray hit distance for the indirect signals, any non-negative value for direct.
float alpha = 0.0f;
if (s == kIndirectDiffuse)
alpha = 0.5f + 3.0f * uniform(pixelId, frameIndex, 10 + s);
else if (s == kIndirectSpecular)
{
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
alpha = bounce.valid ? std::min(bounce.t, 100.0f) : 100.0f;
}
frame.noisy[s][i * 4 + 3] = to_half(alpha);
const V3 albedo = s == kDirectDiffuse || s == kIndirectDiffuse ? diffuse : specular;
combined = combined + mul(clean[s] * sample, albedo);
combinedClean = combinedClean + mul(clean[s], albedo);
}
frame.colorClean[i] = combinedClean;
frame.color[i * 4 + 0] = to_half(combined.x);
frame.color[i * 4 + 1] = to_half(combined.y);
frame.color[i * 4 + 2] = to_half(combined.z);
}
}
} // namespace fsr_scene
+140
View File
@@ -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())
+20 -3
View File
@@ -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])
+155
View File
@@ -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()
+1 -1
View File
@@ -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;
+180 -6
View File
@@ -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(); }
+135
View File
@@ -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()
+2
View File
@@ -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;
+39
View File
@@ -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;
}
+64
View File
@@ -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
View File
@@ -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);
}
};
}
+45 -8
View File
@@ -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";
+22
View File
@@ -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"; }
+125
View File
@@ -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()
+93
View File
@@ -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()
+326
View File
@@ -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, &copy);
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, &copy);
}
};
}
+79
View File
@@ -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;
}
+307
View File
@@ -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, &region);
}
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; }
+291
View File
@@ -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");
}
}
+21
View File
@@ -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();
};
}
+78
View File
@@ -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);
}
};
}
+65
View File
@@ -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);
}
};
}
+347
View File
@@ -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; }
+122
View File
@@ -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");
}
};
}
+88
View File
@@ -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;
}
+179
View File
@@ -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()