Expand Windows GPU support, add native FSR RR, and improve SR interop

Add a native D3D12 FSR Ray Regeneration route for gfx1200/gfx1201,
using the game's device and queue with embedded HLSL guide preparation
and output composition. Preserve the vkd3d compatibility route for
RDNA3 and expose explicit route overrides and diagnostics.

Improve Windows runtime compatibility:
- Retry rejected application-ID initialization with a project identity.
- Defer expensive RR backend creation until feature creation.
- Recover from transient submission failures without disabling later frames.
- Add shared-buffer export fallbacks through explicit heaps and a private
  device on the same adapter.
- Safely evict old RR descriptor sets when input views change.
- Treat requested RR presets as hints when a configured model is available.
- Expand SR/RR input formats and handle diffuse/specular hit distances,
  packed normals, depth-stencil resources, and unused FSR guides.

Reduce SR overhead by batching output downloads and format conversion
under one completion wait. Optimize strict M enc2/tube attention with
bounded FP32 accumulation and the original FP64 fallback for ambiguous
rounding cases.

Extend RDNA4 tooling with gfx1200/gfx1201 native FP8 RR model builds,
capability checks, architecture-aware deployment, and experimental K/M
kernel paths. Require matching model and runtime arithmetic families.

Add optional embedded FSR shader optimizations keyed by shader hash and
exact original SPIR-V contents. Preserve reflected metadata and record
optimizer provenance, artifact hashes, and cache identity.

Simplify Windows installation with automatic backend dependency downloads,
download progress and ETA, backed-up absolute OptiScaler library paths,
and recovery from interrupted RR model builds.

Add build-kernels.bat and a kernels-only build matrix for all eleven
registered RDNA targets, with code-object target validation and SHA256
manifests. Expand conversion probes, numerical stress tests, temporal
benchmark tooling, installer/deployment tests, and setup documentation.

Document synthetic regression and timing results, experimental build
options, and remaining RDNA4 hardware validation requirements.
This commit is contained in:
ayo committed 2026-10-17 07:40:34 -04:00
1 parent a617c818de
commit ac4ee6c225
67 files changed
+3147 -326

No files matched your search

+33 -3
View File
@@ -35,6 +35,18 @@ foreach(index RANGE ${d4r_target_last})
endforeach() endforeach()
configure_file(tools/windows/gpu_targets.generated.h.in gpu_targets.generated.h @ONLY) configure_file(tools/windows/gpu_targets.generated.h.in gpu_targets.generated.h @ONLY)
include_directories("${CMAKE_CURRENT_BINARY_DIR}") include_directories("${CMAKE_CURRENT_BINARY_DIR}")
foreach(pass prepare compose)
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/tools/windows/fsr_rr/fsr_rr_${pass}.hlsl" d4r_hlsl_fsr_rr_${pass})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "tools/windows/fsr_rr/fsr_rr_${pass}.hlsl")
endforeach()
configure_file(tools/windows/fsr_rr_shaders_hlsl.h.in fsr_rr_shaders_hlsl.generated.h @ONLY)
add_executable(d4r_fsr_rr_native_probe tools/windows/fsr_rr_native_probe.cpp)
target_include_directories(d4r_fsr_rr_native_probe PRIVATE "${D4R_HIP_ROOT}/include")
target_compile_definitions(d4r_fsr_rr_native_probe PRIVATE __HIP_PLATFORM_AMD__ NOMINMAX WIN32_LEAN_AND_MEAN)
target_link_libraries(d4r_fsr_rr_native_probe PRIVATE d3d12 dxgi dxguid)
if(MINGW)
target_link_options(d4r_fsr_rr_native_probe PRIVATE -static)
endif()
set(D4R_GPU_ARCH "gfx1201" CACHE STRING "Native Windows package GPU target") set(D4R_GPU_ARCH "gfx1201" CACHE STRING "Native Windows package GPU target")
set_property(CACHE D4R_GPU_ARCH PROPERTY STRINGS ${d4r_supported_targets}) set_property(CACHE D4R_GPU_ARCH PROPERTY STRINGS ${d4r_supported_targets})
if(NOT D4R_GPU_ARCH IN_LIST d4r_supported_targets) if(NOT D4R_GPU_ARCH IN_LIST d4r_supported_targets)
@@ -42,8 +54,8 @@ if(NOT D4R_GPU_ARCH IN_LIST d4r_supported_targets)
endif() endif()
add_compile_definitions(D4R_TARGET_ARCH="${D4R_GPU_ARCH}") add_compile_definitions(D4R_TARGET_ARCH="${D4R_GPU_ARCH}")
configure_file(tools/windows/gpu-target.json.in gpu-target.json @ONLY) configure_file(tools/windows/gpu-target.json.in gpu-target.json @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gpu-target.json" DESTINATION .) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gpu-target.json" DESTINATION . COMPONENT kernels)
install(FILES tools/windows/gpu-targets.json DESTINATION .) install(FILES tools/windows/gpu-targets.json DESTINATION . COMPONENT kernels)
set(d4r_device_flags) set(d4r_device_flags)
if(D4R_GPU_ARCH MATCHES "^gfx11") if(D4R_GPU_ARCH MATCHES "^gfx11")
# Windows-only workaround informed by realdody/d4r 1897b4a. Keep the real # Windows-only workaround informed by realdody/d4r 1897b4a. Keep the real
@@ -115,13 +127,21 @@ foreach(family k m)
DEPENDS "${source}" ${native_headers} kernels/common/wmma_layout.h kernels/common/wave32_exchange.h DEPENDS "${source}" ${native_headers} kernels/common/wmma_layout.h kernels/common/wave32_exchange.h
kernels/common/hip_device_minimal.h VERBATIM) kernels/common/hip_device_minimal.h VERBATIM)
list(APPEND native_objects "${native_object}") list(APPEND native_objects "${native_object}")
install(FILES "${native_object}" DESTINATION "experimental/${family}") install(FILES "${native_object}" DESTINATION "experimental/${family}" COMPONENT kernels)
endforeach() endforeach()
endforeach() endforeach()
add_custom_target(d4r_native_compile ALL DEPENDS ${native_objects}) add_custom_target(d4r_native_compile ALL DEPENDS ${native_objects})
# Retain the old manual build target for existing scripts. # Retain the old manual build target for existing scripts.
add_custom_target(d4r_native_gfx1201_compile DEPENDS d4r_native_compile) add_custom_target(d4r_native_gfx1201_compile DEPENDS d4r_native_compile)
# Compile every native object without building the host tools or fetching their
# dependencies. Use the same flags and sources as the complete Windows package.
option(D4R_KERNELS_ONLY "Configure only Windows HIP kernel compilation" OFF)
if(D4R_KERNELS_ONLY)
install(FILES "${probe_object}" "${pixel_object}" "${wmma_object}" DESTINATION bin COMPONENT kernels)
return()
endif()
# Standalone Vulkan inference does not depend on the D3D12 NGX shim. Volk loads # Standalone Vulkan inference does not depend on the D3D12 NGX shim. Volk loads
# the Windows Vulkan runtime dynamically, so no import library is required. # the Windows Vulkan runtime dynamically, so no import library is required.
option(D4R_BUILD_NATIVE_ENGINE "Build standalone K/M/L Vulkan inference runners" ON) option(D4R_BUILD_NATIVE_ENGINE "Build standalone K/M/L Vulkan inference runners" ON)
@@ -365,6 +385,16 @@ install(FILES kernels/tools/model_enc3.py kernels/tools/swin_model.py kernels/to
install(FILES docs/windows-rdna4-port.md DESTINATION docs) install(FILES docs/windows-rdna4-port.md DESTINATION docs)
install(FILES docs/windows-gpu-support.md DESTINATION docs) install(FILES docs/windows-gpu-support.md DESTINATION docs)
include(CTest) include(CTest)
add_test(NAME fsr_rr_native_conversion COMMAND d4r_fsr_rr_native_probe)
add_test(NAME fsr_rr_native_conversion_warp COMMAND d4r_fsr_rr_native_probe --warp)
add_test(NAME fsr_rr_native_sky COMMAND d4r_fsr_rr_native_probe --sky)
add_test(NAME fsr_rr_native_hardware_depth COMMAND d4r_fsr_rr_native_probe --hardware-depth)
add_test(NAME fsr_rr_native_depth_stencil COMMAND d4r_fsr_rr_native_probe --depth-stencil)
add_test(NAME fsr_rr_native_subrect COMMAND d4r_fsr_rr_native_probe --subrect)
set_tests_properties(fsr_rr_native_conversion fsr_rr_native_sky fsr_rr_native_hardware_depth
fsr_rr_native_depth_stencil fsr_rr_native_subrect
PROPERTIES TIMEOUT 60 LABELS "hardware;d3d12;fsr;conversion")
set_tests_properties(fsr_rr_native_conversion_warp PROPERTIES TIMEOUT 60 LABELS "software;d3d12;fsr;conversion")
add_test(NAME binary16_rounding COMMAND d4r_binary16_probe) add_test(NAME binary16_rounding COMMAND d4r_binary16_probe)
set_tests_properties(binary16_rounding PROPERTIES TIMEOUT 30 LABELS "software;numerics") set_tests_properties(binary16_rounding PROPERTIES TIMEOUT 30 LABELS "software;numerics")
add_test(NAME gpu_target_contract COMMAND d4r_gpu_target_probe "${probe_object}" "${pixel_object}") add_test(NAME gpu_target_contract COMMAND d4r_gpu_target_probe "${probe_object}" "${pixel_object}")
+16 -3
View File
@@ -1,6 +1,19 @@
# d4r (dlss 4 radeon) ## Windows
d4r is currently overhauling the windows build. Extract the Windows release ZIP beside the game's main executable, run `install.bat`,
then launch the game normally and select DLSS. The installer downloads and verifies
the required dependencies; Python and a compiler are included where needed.
Super Resolution setup needs no GPU selection or Ray Reconstruction model build.
See [Windows installation and settings](packaging/windows/WINDOWS_README.txt).
To compile the Windows K/M kernels for every registered RDNA 3, 3.5 and 4 target,
run `build-kernels.bat` from the source checkout. On a new build machine, first run
`powershell -NoProfile -ExecutionPolicy Bypass -File scripts\windows\setup-windows-tools.ps1 -RuntimeProfile therock`
(requires Python). Outputs go to `dist/windows-kernels-<gfx target>`;
`build/windows-kernels/matrix-build.json` records hashes and target checks.
No GPU or NVIDIA DLL is required for this kernel build. These kernel outputs are
build artifacts; use the release ZIP to install into a game.
See [GPU coverage and full release builds](docs/windows-gpu-support.md).
> **Not affiliated with NVIDIA or AMD.** > **Not affiliated with NVIDIA or AMD.**
@@ -10,7 +23,7 @@ RX 7700 XT on the linux build, windows should get similar performance (plus ts l
[DLSS Ultra Performance demo video](https://cdn.ayois.gay/dlss). [DLSS Ultra Performance demo video](https://cdn.ayois.gay/dlss).
More up to date videols (0.1.4), still linux build More up to date videos (0.1.4), still linux build
**Ready or Not** **Ready or Not**
+7
View File
@@ -0,0 +1,7 @@
@echo off
setlocal
rem Build all registered RDNA 3, 3.5 and 4 targets. No GPU or NVIDIA DLL needed.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\windows\build-windows-gpu-matrix.ps1" -KernelsOnly -BuildRoot "%~dp0build\windows-kernels" -OutputRoot "%~dp0dist\windows-kernels" %*
set "D4R_BUILD_EXIT=%ERRORLEVEL%"
if not "%D4R_BUILD_EXIT%"=="0" echo Kernel build failed. See build\windows-kernels for logs. Run scripts\windows\setup-windows-tools.ps1 -RuntimeProfile therock if tools are missing.
exit /b %D4R_BUILD_EXIT%
+96 -1
View File
@@ -1,5 +1,66 @@
# Native Windows FSR Ray Regeneration # Native Windows FSR Ray Regeneration
## Native D3D12 route for RDNA4 (2026-10-17)
The source now selects **native D3D12 on gfx1200/gfx1201** when
`[RayReconstruction] Backend = fsr`. AMD's original denoiser receives the game's
real D3D12 device and records on its queue. D3D12 compute shaders perform the
guide conversion and composition on that same device. This route does not load
the vkd3d core, the amdxc shim, or Vulkan; it does not redirect AMD's driver lookup
or apply the RDNA3 compiler workaround.
Guides are copied into shader-readable textures on the same device, including
depth resources that deny shader reads. This retains subrect and guide-format
handling while removing the shared cross-API packet. Composition goes directly
to the internal SR stage, or copies into the game's output without SR. RGBA32F
output widens the completed half image on the GPU. Evaluation remains synchronous:
one completion wait retires the denoiser list before resource reuse and SR; this
is not an asynchronous backend. AMD's denoiser/context may still compile shaders
on first use. No RDNA4 timing or quality improvement has been measured.
RDNA3 keeps the existing vkd3d compatibility route below. Diagnostic environment
`D4R_FSR_RR_DEVICE=auto|native|vkd3d` overrides selection; `auto` is the default.
Native provider/context failures are reported explicitly. An override can select
the compatibility route for diagnosis. Readiness checks require only AMD's
denoiser DLL for native D3D12, and retain the extra payload requirements for vkd3d.
The dependency installer permits omitting that payload only for explicitly
RDNA4-only packages; mixed packages still require it. The native adapter compiles
its embedded conversion shaders with Windows' system `d3dcompiler_47.dll`.
Validation on the available RX 7700 XT (gfx1101):
- Six native conversion tests pass, including WARP, hardware depth, depth-stencil
with denied shader reads, subrects with sentinel padding, invalid-normal sky
passthrough, and exact half-to-float output widening. Each case checks all
4,355 pixels against independent expected values. These tests do **not** execute
AMD's denoiser. Native D3D12 float-to-half stores use their specified truncation;
cross-backend bit equality is not asserted.
- All 22 CTest checks, 13 RR Python tests and 13 installer tests pass.
- The automatic RDNA3 compatibility route passes 24 moving frames with guide and
output format switching, typeless D32S8 depth and exact same-frame widening
(`build/fsr-native-rdna3-compat-formats-v1/`). Another 24 frames pass dynamic
render extents, retained creation-sized resources, SR-bound rejection and SR
output (`build/fsr-native-rdna3-compat-dynamic-sr-v1/`).
- Explicit native selection on gfx1101 without the compatibility DLLs reaches
AMD's provider query and returns `no FSR Ray Regeneration provider for this
device`, as expected. It does not silently switch routes.
- The gfx1200/gfx1201 shims and gfx1201 native conversion probe compile successfully. **Full native
RDNA4 denoising, SR handoff, temporal quality, real games and performance still
require hardware validation.** Existing installed packages were not replaced.
Run the conversion checks with:
```powershell
cmake --build BUILD --target d4r_fsr_rr_native_probe
ctest --test-dir BUILD -R fsr_rr_native --output-on-failure
```
For an RDNA4 synthetic integration run, use a matching target shim/probe and
`tools/windows/test_fsr_rr_ngx.py --device native` with its usual fixture/runtime
arguments. That mode removes compatibility DLLs from the private staged runtime
and verifies the logged route. Include ordinary SR and dynamic-resolution runs
before claiming end-to-end validation.
## Status (2026-10-17): runs on RDNA3 through vkd3d-proton; synthetic validation only ## Status (2026-10-17): runs on RDNA3 through vkd3d-proton; synthetic validation only
`[RayReconstruction] Backend = fsr` answers a game's DLSS RR request (NGX feature 13) with AMD's FSR Ray `[RayReconstruction] Backend = fsr` answers a game's DLSS RR request (NGX feature 13) with AMD's FSR Ray
@@ -13,7 +74,7 @@ Windows drivers therefore offer no provider. Linux gets one through vkd3d-proton
fallback and a Proton `amdxc64.dll` shim. The Windows route reproduces that: fallback and a Proton `amdxc64.dll` shim. The Windows route reproduces that:
1. **vkd3d-proton for Windows** (`tools/windows/build_fsr_payload.py`): vkd3d-proton `3dfc6f07` plus dxil-spirv 1. **vkd3d-proton for Windows** (`tools/windows/build_fsr_payload.py`): vkd3d-proton `3dfc6f07` plus dxil-spirv
PR 307 (`f9f6294f`) and `-Denable_extended_emulation=true`, built with llvm-mingw clang; two libc++ include PR 307 (`f9f6294f`) and `-Denable_extended_emulation=true`, built with llvm-mingw clang; local Windows
patches in `patches/vkd3d-proton-windows/`. The shipped `d4r\fsr\d4r_vkd3d_core.dll` is `d3d12core.dll` patches in `patches/vkd3d-proton-windows/`. The shipped `d4r\fsr\d4r_vkd3d_core.dll` is `d3d12core.dll`
renamed and loaded by path; `IVKD3DCoreInterface::CreateDevice` is called directly (the IDL has no base renamed and loaded by path; `IVKD3DCoreInterface::CreateDevice` is called directly (the IDL has no base
interface, so it is vtable slot 0). `DXIL_SPIRV_CONFIG=wmma_rdna3_workaround` is set in-process. interface, so it is vtable slot 0). `DXIL_SPIRV_CONFIG=wmma_rdna3_workaround` is set in-process.
@@ -133,6 +194,40 @@ Evidence: `build/fsr-perf-720-{baseline-v2,baseline-repeat-v1,unroll-v2,wave32-v
the shipped payload stays `build/fsr-payload-v1`. The two third-party source files edited for experiments the shipped payload stays `build/fsr-payload-v1`. The two third-party source files edited for experiments
were restored; pre-existing libc++ include fixes remain. Linux denoiser performance parity is still open. were restored; pre-existing libc++ include fixes remain. Linux denoiser performance parity is still open.
## Local shader optimization payload (2026-10-17)
The payload builder now preserves dxil-spirv's reflected shader metadata when replacing compute shaders.
An earlier replacement path returned before reflecting the required wave size; even a byte-identical
replacement could therefore change the output. Patch `0003-preserve-meta-for-shader-overrides.patch`
moves all replacement to the existing late path.
Optional local optimizations use `--shader-dump DIR --spirv-opt PATH --optimize-shader HASH` (repeat
the final argument for each measured shader). `--shader-variant DIR` adds another exact original
translation, such as the existing NGX rounding-mode variant, without changing its arithmetic settings.
The builder runs `spirv-opt --target-env=vulkan1.3 -O`,
validates each result, and embeds the original and optimized modules in the private core. A replacement
requires both the DXIL shader hash and a byte-for-byte match of the original translation. Different
translator options, resource layouts, or devices that produce different SPIR-V retain their original
translation. No precision-widening flag or change to FP8 conversion/quantization is added.
The source inputs, generated header, optimizer hash, and output hashes are saved in `shader-sources/`
and `fsr-payload.json`. The cache build ID incorporates local patches and the optimization manifest,
so the new core rejects the old payload's compiled shader cache. No external shader folder or global
override environment is needed at runtime. `D4R_FSR_DISABLE_BUILTIN_SHADERS=1` selects an unmodified
control; use a separate cache directory when comparing this control with enabled replacements.
On this RX 7700 XT, applying `-O` to every shader helps some layers and hurts others. The local payload
selects seven measured winners: `09749b7dfc3a7804`, `430ab44dc42ba8b5`, `5bec91d51f1d2038`,
`c7a9797f50bb6526`, `e34e88f998f02da9`, `e7d13b75c921986c`, and `f8459f275005d65e`.
At 1280x720 with two direct signals and NGX's existing arithmetic settings, the probe GPU median is
34.374 -> 32.846 ms (4.4% lower). Full NGX late-frame denoiser medians are 31.58 -> 30.88 ms and
31.81 -> 31.04 ms on a repeat (2.2-2.4% lower). NGX tail RMSE changes by +0.41% / +0.33%, within the
existing run-to-run noise; all seven replacements activate. These are synthetic GPU timings, not game FPS.
This is a modest Windows optimization, not Linux performance parity. Actual loop expansion and
straight-line cooperative-matrix component conversion did not improve the measured result beyond `-O`
and are not included. Full measurements and deployment details are in
`C:\Users\ayo\Desktop\d4r-windows-fsr-results-20261017-0333\SUMMARY-selected-O.md`.
## History: the investigation before the vkd3d route ## History: the investigation before the vkd3d route
## Corrected provider descriptor recheck ## Corrected provider descriptor recheck
+10
View File
@@ -114,6 +114,16 @@ compatibility; actual targets follow the selected architecture.
One command compiles all eleven targets without GPU workloads: One command compiles all eleven targets without GPU workloads:
For just the K/M kernels, pixel conversion and two device probes, run
`build-kernels.bat` at the repository root after tool setup. It compiles 19
code objects for each target using the same CMake flags as the full build,
checks their actual ELF targets, and writes per-object SHA256 hashes to
`build/windows-kernels/matrix-build.json`. Outputs are separate under
`dist/windows-kernels-<target>`. No ZLUDA, NVIDIA DLL or GPU execution is
required. This does not produce an installable game release.
For the complete host diagnostics and kernels:
```powershell ```powershell
.\scripts\windows\build-windows-gpu-matrix.ps1 -ZludaRoot "$PWD\dist\zluda-windows-final" .\scripts\windows\build-windows-gpu-matrix.ps1 -ZludaRoot "$PWD\dist\zluda-windows-final"
``` ```
+2
View File
@@ -1,5 +1,7 @@
# Windows K performance work # Windows K performance work
Latest K/M follow-up: [Windows SR optimization](windows-sr-optimization.md) records output batching, bounded strict M attention, the local v20 package, and exact final-loader checks. Linux parity remains unverified.
## Current optimization pass: strict DS exchange, 1080p and 1440p (2026-10-17) ## Current optimization pass: strict DS exchange, 1080p and 1440p (2026-10-17)
After the position-layer barrier fix, the explicit gfx11 DS cross-half exchange passes the strict gate. After the position-layer barrier fix, the explicit gfx11 DS cross-half exchange passes the strict gate.
+84
View File
@@ -0,0 +1,84 @@
# RDNA 4 optimization candidates (2026-10-17)
These changes expose target-specific paths for gfx1200 and gfx1201. The available
GPU is an RX 7700 XT (gfx1101), so **no RDNA 4 speedup or output equivalence has
been measured**. Compilation and instruction checks do not establish either.
Existing packages and games were not updated.
## Paths
- **K:** `build-native-k-experiment.ps1 -F16Wmma` exposes the existing gfx12 native
FP16-accumulator WMMA implementation. It requires the strict K baseline, rejects
gfx11 and incompatible packed-accumulator storage, and records the selection in
candidate metadata. It remains experimental pending layer and temporal-frame
equality checks. The default kernel arithmetic is unchanged.
- **M:** `build-native-m-fp8.ps1 -GpuArch gfx1200|gfx1201` builds the existing native
FP8 path for either target, including optional hardware packing and token lanes.
The ISA gate requires FP8 WMMA instructions. The separate strict FP16 candidate
builder now supports both gfx12 targets with native lane exchange; gfx11-only
LDS/DS flags are excluded. Neither candidate is selected automatically.
- **DLSS RR D/E:** the Windows model builder now accepts `--gpu-arch gfx1200|gfx1201`.
It uses the existing native FP8 cooperative-matrix shaders, omits gfx11's widened
weight copies and records `arithmetic gfx12`. Every native network shader must
declare Float8EXT, Float8CooperativeMatrixEXT and CooperativeMatrixKHR.
The Windows Vulkan bridge enables and requires the float8 extension/features on
gfx12. Model deployment requires matching runtime `gpu-target.json`; the bridge
rejects a model from the other arithmetic family. Exact conversion decorations
remain enabled; relaxed arithmetic is not introduced. The NGX regression helper
accepts the same `--gpu-arch` to select its format module and native directory.
- **FSR RR:** gfx12 no longer receives `wmma_rdna3_workaround` by default. The local
dxil-spirv implementation applies gfx11-specific column mapping and FP16 RTZ
under that setting. gfx11 retains it. Explicit process overrides survive; lookup
uses the Windows environment rather than the C runtime's stale snapshot.
This removes an inappropriate target setting; native FSR execution and its
performance still depend on the target Vulkan driver's capabilities.
A subsequent change now selects direct D3D12 on gfx12 by default, bypassing
this compatibility route entirely. See [native route status](windows-fsr-rr.md).
AMD documents RDNA 4's native FP8 matrix support in its
[matrix-core guide](https://gpuopen.com/learn/using_matrix_core_amd_rdna4/).
That hardware capability does not establish Windows Vulkan driver support.
## Build examples
Use a fresh ignored output directory for each candidate. Replace DLL paths with
the locally supplied pinned binaries. Do not install candidates before validating
their outputs and paired timings on the target GPU.
```powershell
scripts/windows/build-native-k-experiment.ps1 -GpuArch gfx1201 -Layer enc1 -F16Wmma -OutputDirectory build/my-k-gfx1201
scripts/windows/build-native-m-fp8.ps1 -GpuArch gfx1201 -DlssDll PATH/nvngx_dlss.dll -HardwarePacking -TokenLanes -OutputDirectory build/my-m-gfx1201
python tools/windows/build_rr_model.py --gpu-arch gfx1201 --preset D --dll PATH/nvngx_dlssd.dll --output build/my-rr-d-gfx1201
```
Use gfx1200 consistently for that target. Build the shim/probe with the matching
`D4R_GPU_ARCH`. Deployment can stage a verified private model with
`deploy_rr_model.py --model MODEL --runtime RUNTIME`; selection still requires
`--enable` or an explicit model path. No relaxed model is built by this recipe.
## Checks performed
- K enc1 FP16 WMMA candidates compile for both targets. All eleven baseline K and
five baseline M objects additionally compile for gfx1201.
- All five M native FP8 modules compile for both targets, with native WMMA and
hardware packing instructions verified by the builder. Strict FP16 M candidate
sets also compile for both targets.
- Complete D/gfx1201 and E/gfx1200 RR models compile. All eleven native network
stages per model pass the SPIR-V native FP8 capability gate.
- The shim and standalone RR runner build for gfx1201; the gfx1101 shim, probe and
runner also rebuild successfully.
- 13 RR Python tests and 16 existing CTest checks pass on gfx1101. Some legacy
CTest names contain gfx1201/gfx12; these runs used the configured gfx1101 target.
- Native DLSS RR passes six synthetic camera/reset frames against the existing
reference, byte-for-byte. Evidence: `build/rdna4-opt-rr-rdna3-regression-v3/`.
- FSR RR passes 24 moving-camera synthetic frames with exact same-frame output
widening. Tail RMSE is approximately 0.1066 versus noisy input 0.4862. The log
confirms gfx1101 still selects `wmma_rdna3_workaround`. Evidence:
`build/rdna4-opt-fsr-rdna3-regression-v3/`.
The additional NGX `--contracts --without-sr` run fails at
`RR advertised on unsupported WARP adapter`, before inference. That capability
contract is unresolved; the successful six-frame run does not clear it.
Candidate artifacts remain under `build/rdna4-opt-*`. RDNA 4 layer/frame equality,
real-game quality, GPU timings and paired performance comparisons remain open.
+63
View File
@@ -0,0 +1,63 @@
# Windows K/M optimization (2026-10-17)
The local gfx1101 v20 package reduces SR interop overhead and the cost of strict M attention. Linux performance parity remains unverified. Measurements below are synthetic CPU interop times, including NGX, copies and synchronization; they are not GPU kernel times or game frame times.
## Changes
- The output CUDA array download and optional HIP format conversion now share one ordered legacy-stream batch. One whole-context completion precedes the D3D12 output signal. NGX's all-stream completion still precedes the batch. Output diagnostics insert their own completion before inspecting the canonical image.
- M enc2 and tube layers first sum their 16 FP16 products in FP32. Each product is exact in FP32. An error bound larger than gamma(16), including magnitude-sum error, determines whether the result is safely inside one FP16 rounding interval. Ambiguous intervals, cancellation, overflow and nonfinite cases retain the original sequential FP64 dot. Quantization, weights, rounding and tensor layouts are unchanged. Other M layers retain FP64 because the candidate regressed their replay timings.
- The local v20 package enables the existing `BatchInputCopies` setting. The source INI and parser fallback remain false; older installations retain their selection. This local package targets the tested RX 7700 XT only.
- The standalone engine build script now requests Release. This did not improve measured K frame timing and is not counted as a speedup.
## Final package checks
Final candidates use the packaged loader, rebuilt optimized shim and the actual CMake-built M objects. Controls use v19's shim and native objects. Runs alternate control/candidate/candidate/control, with 24 temporal frames per process and four warmup frames excluded from timing. All output hashes match exactly across all four processes. All frames pass the harness's finite-output check.
K render sizes are 1280x720 and 1706x960; M render sizes are 960x540 and 1280x720.
| Preset and mode | Output | v19 CPU interop median | v20 CPU interop median | Reduction |
|---|---|---:|---:|---:|
| K Quality | 1920x1080 | 3.190 ms | 3.063 ms | 4.0% |
| K Quality | 2560x1440 | 4.651 ms | 4.606 ms | 1.0% |
| M Performance | 1920x1080 | 11.107 ms | 10.251 ms | 7.7% |
| M Performance | 2560x1440 | 18.372 ms | 17.186 ms | 6.5% |
The small K 1440p difference is within observed run variation; do not treat it as a demonstrated GPU speedup. Earlier isolated input-batching runs measured 3.187 -> 3.092 ms and 4.690 -> 4.496 ms, with 128 exact frames per size. CPU load and synchronization make these timings variable.
Packed-format checks add 64 exact K frames per profile at 1280x720. R11G11B10 output measured 2.705 -> 2.566 ms, and BGRA8 output measured 2.726 -> 2.341 ms. Both native packed bytes and decoded half-image bytes are compared. These exercise the deferred output conversion as well as input batching. These format-specific timings must not be projected to other formats or games.
Final package evidence:
- `build/sr-opt-final-K-1920x1080-v1/summary.json`
- `build/sr-opt-final-K-2560x1440-v1/summary.json`
- `build/sr-opt-final-M-1920x1080-v1/summary.json`
- `build/sr-opt-final-M-2560x1440-v1/summary.json`
- `build/sr-opt-final-K-packed-v1/summary.json`
- `build/sr-opt-final-K-unorm-v1/summary.json`
The final package passes 512 exact temporal frames across these cases. This is same-GPU regression evidence, not NVIDIA reference-quality validation. Other GPUs, M Quality, real games and RR behavior were not validated in this pass.
## Numerical and build checks
`build/m-opt-pv-stress-v2/summary.json` records 262,144 GPU dots with zero differing FP16 bits against the original FP64 sum. Inputs include signed products over the finite FP16 exponent range, cancellation, every finite FP16 magnitude, rounding midpoints and tiny signed perturbations. All five synthetic M layer replays also match every allocation byte. Only enc2/tube improved sufficiently to select the bounded path.
Reproduce the dot check:
```powershell
$sdk = '.tools/therock-10.2.0a20260929/_rocm_sdk_core'
& "$sdk/lib/llvm/bin/clang++.exe" -x hip -std=c++17 --offload-arch=gfx1101 `
--offload-device-only --no-gpu-bundle-output -mno-wavefrontsize64 -nogpuinc -nogpulib `
-O3 -DD4R_DEVICE_ONLY_MINIMAL tools/windows/exact_pv_probe.hip -o build/exact_pv_probe.hsaco
python tools/windows/test_exact_pv.py --probe build/windows-rdna4-therock-gfx1101/d4r_native_replay_probe.exe `
--hip-root $sdk --module build/exact_pv_probe.hsaco --output-dir build/exact-pv-fresh-run
```
`scripts/windows/build-native-m-experiment.ps1` builds strict M candidates without installing them. `-ReferencePv` restores the FP64-only control. K's experimental builder also accepts `-LaneExchange native`; it matched the tested 1080p case but provided no gain. K DS exchange remains selected. M LDS/DS flag comparisons provided no gain. Release versus unoptimized standalone K runners matched all five frame products but showed no timing improvement. Standalone K remains roughly 1.85 ms at 1080p and 2.91 ms at 1440p, above the reported Linux targets.
`build/sr-opt-player-config-v1/summary.json` passes the INI/environment and loader-selection checks. Both relevant CMake builds pass. Python compilation and PowerShell parsing pass for the changed tools.
The texture builder additionally accepts M folded post variants and an experimental `--wave64` / `-Wave64` option. It verifies the emitted wave64 target feature and retains strict arithmetic. Such objects contain NVIDIA-derived code and stay in ignored local build directories; they are excluded from v20.
The tested M HDR/render-motion post variant passes another 96 exact frames per resolution (`build/m-post-wave64-{1920x1080,2560x1440}-v1/`). Its CPU interop difference is small and varies between runs; it is not promoted. Enc0/dec0/post/downsample still use the translated texture path in v20. Matching Linux's broader native texture coverage and reducing cross-API overhead remain open work.
Accepted transient raw readbacks were hashed before pruning; summaries, logs, hashes and first-frame previews remain. Inventories are `build/sr-opt-readback-cleanup-v1.json` and `build/sr-opt-readback-cleanup-v2.json`. Earlier work and user data were not pruned.
+10 -3
View File
@@ -122,8 +122,8 @@ RModel::RModel(VkPhysicalDevice physical, VkDevice device, const std::string& di
if (key == "preset") f >> m.preset; if (key == "preset") f >> m.preset;
else if (key == "arithmetic") { else if (key == "arithmetic") {
std::string arithmetic; f >> arithmetic; std::string arithmetic; f >> arithmetic;
if (arithmetic != "gfx11") throw std::runtime_error("rr.txt: unknown arithmetic " + arithmetic); if (arithmetic != "gfx11" && arithmetic != "gfx12") throw std::runtime_error("rr.txt: unknown arithmetic " + arithmetic);
m.gfx11 = true; m.gfx11 = arithmetic == "gfx11";
} }
else if (key == "stage") { else if (key == "stage") {
std::string name, shader, weightFile, textureList, surfaceList, option; uint32_t subgroup = 32; std::string name, shader, weightFile, textureList, surfaceList, option; uint32_t subgroup = 32;
@@ -519,7 +519,14 @@ void REngine::setFrame(const RFrameImages& images, const RFrameSettings& setting
}; };
auto found = std::find_if(e.bindings.begin(), e.bindings.end(), same); auto found = std::find_if(e.bindings.begin(), e.bindings.end(), same);
if (found == e.bindings.end()) { if (found == e.bindings.end()) {
if (e.bindings.size() >= 16) throw std::runtime_error("too many distinct Ray Reconstruction input view sets"); // Games toggle optional guides and change guide formats (Cyberpunk 2077 passed 16 distinct sets within
// seconds). Evict the oldest set instead of failing: its descriptors may still be referenced by submitted
// work, so the device drains first. Eviction is rare once the working set is cached.
if (e.bindings.size() >= 16) {
VKCHECK(vkDeviceWaitIdle(d.handle));
vkDestroyDescriptorPool(d.handle, e.bindings.front().pool, nullptr);
e.bindings.erase(e.bindings.begin());
}
Impl::Bindings b; Impl::Bindings b;
b.images = images; b.finalOutput = finalOutput; b.images = images; b.finalOutput = finalOutput;
uint32_t textures = 0, surfaces = 0; uint32_t textures = 0, surfaces = 0;
+6 -1
View File
@@ -561,7 +561,12 @@ struct R2Engine {
}; };
auto found = std::find_if(bindings.begin(), bindings.end(), same); auto found = std::find_if(bindings.begin(), bindings.end(), same);
if (found == bindings.end()) { if (found == bindings.end()) {
if (bindings.size() >= 16) throw std::runtime_error("too many distinct Ray Reconstruction input view sets"); // Evict the oldest set rather than failing (see runtime_rr.inc); drain its in-flight uses first.
if (bindings.size() >= 16) {
VKCHECK(vkDeviceWaitIdle(d.handle));
vkDestroyDescriptorPool(d.handle, bindings.front().pool, nullptr);
bindings.erase(bindings.begin());
}
Bindings b; Bindings b;
b.images = images; b.finalOutput = finalOutput; b.images = images; b.finalOutput = finalOutput;
const uint32_t tiles = static_cast<uint32_t>(src.size()); const uint32_t tiles = static_cast<uint32_t>(src.size());
+3
View File
@@ -3,5 +3,8 @@
#define SWIN_F32ACC #define SWIN_F32ACC
#define SWIN_MAX_VGPR 192 #define SWIN_MAX_VGPR 192
#define SWIN_MLP_R(NW) 2 #define SWIN_MLP_R(NW) 2
#if defined(D4R_WINDOWS_GFX11_LANE_EXCHANGE) && defined(__GFX11__)
#define SWIN_BOUNDED_PV // retain FP64 only when the FP16 rounding interval is ambiguous
#endif
#include "swin_block.h" #include "swin_block.h"
SWIN_MODULE_W(rrlite_enc2_4x4, 96, 4, 8, 128, false, CommonParams, 0) SWIN_MODULE_W(rrlite_enc2_4x4, 96, 4, 8, 128, false, CommonParams, 0)
+3
View File
@@ -3,6 +3,9 @@
#define SWIN_F32ACC #define SWIN_F32ACC
#define SWIN_MAX_VGPR 192 #define SWIN_MAX_VGPR 192
#define SWIN_MLP_R(NW) 2 #define SWIN_MLP_R(NW) 2
#if defined(D4R_WINDOWS_GFX11_LANE_EXCHANGE) && defined(__GFX11__)
#define SWIN_BOUNDED_PV // retain FP64 only when the FP16 rounding interval is ambiguous
#endif
#define SWIN_PREP_SLOTS 8 // one weight image per tube block (six per frame) #define SWIN_PREP_SLOTS 8 // one weight image per tube block (six per frame)
#include "swin_block.h" #include "swin_block.h"
SWIN_MODULE_W(rrlite_enc3_tube_4x4, 128, 4, 8, 0, true, TubeParams, 0) SWIN_MODULE_W(rrlite_enc3_tube_4x4, 128, 4, 8, 0, true, TubeParams, 0)
+1 -4
View File
@@ -458,10 +458,7 @@ __device__ __forceinline__ void swin_block(const CommonParams& p, const TubePara
// FP16 neighbour at a midpoint, then flip the FP8 output code. // FP16 neighbour at a midpoint, then flip the FP8 output code.
// This baseline sums the 16 FP16 products before one FP16 round. // This baseline sums the 16 FP16 products before one FP16 round.
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
double dot = 0.0; o[i] = (float)exact_pv_dot(Qs + mrow(i) * PST, Vs + 16 * nt + l16);
for (int k = 0; k < 16; ++k)
dot += (double)Qs[mrow(i) * PST + k] * (double)Vs[k * 32 + 16 * nt + l16];
o[i] = (float)(half_t)dot;
} }
#endif #endif
const int ocol = apos(32 * h + ginv(16 * nt + l16)); const int ocol = apos(32 * h + ginv(16 * nt + l16));
+34
View File
@@ -206,6 +206,40 @@ __device__ __forceinline__ half_t f16(float v)
return (half_t)v; return (half_t)v;
} }
// FP16 products are exact in FP32. Bound the error of the 16 additions,
// including the reference's FP64 additions, before accepting an FP16 round.
// Cancellation and midpoint cases retain the original FP64 summation order.
template <int VStride = 32>
__device__ __forceinline__ half_t exact_pv_dot(const half_t* p, const half_t* v)
{
#if defined(SWIN_BOUNDED_PV) && !defined(SWIN_EXACT_PV_REFERENCE)
float sum = 0.0f, magnitude = 0.0f;
#pragma unroll
for (int k = 0; k < 16; ++k) {
const float product = (float)p[k] * (float)v[k * VStride];
sum += product;
magnitude += __builtin_fabsf(product);
}
const half_t rounded = (half_t)sum;
const uint16_t bits = __builtin_bit_cast(uint16_t, rounded) & 0x7fffu;
if (bits < 0x7bffu) {
const float center = (float)__builtin_bit_cast(half_t, bits);
const float next = (float)__builtin_bit_cast(half_t, (uint16_t)(bits + 1));
const float previous = bits ? (float)__builtin_bit_cast(half_t, (uint16_t)(bits - 1)) : -next;
const float absolute = __builtin_fabsf(sum);
const float distance = __builtin_fminf(absolute - (previous + center) * 0.5f,
(center + next) * 0.5f - absolute);
// 2^-18 exceeds gamma(16) plus the error in the magnitude sum.
// The bound is normal FP32 even for the smallest FP16 product.
if (distance > magnitude * 0x1p-18f) return rounded;
}
#endif
double dot = 0.0;
for (int k = 0; k < 16; ++k)
dot += (double)p[k] * (double)v[k * VStride];
return (half_t)dot;
}
__device__ __forceinline__ half_t rsqrt16(half_t s) __device__ __forceinline__ half_t rsqrt16(half_t s)
{ {
return f16(1.0f / __builtin_sqrtf((float)(s + (half_t)(1.0f / 8192.0f)))); return f16(1.0f / __builtin_sqrtf((float)(s + (half_t)(1.0f / 8192.0f))));
+8 -4
View File
@@ -11,7 +11,9 @@ SUPPORTED_GAMES.md in the source repository lists the tested games. Expect probl
What you need What you need
------------- -------------
- Windows 11, a Radeon RX 7000 or RX 9000 series GPU, a current AMD Adrenalin driver. - Windows 11, a Radeon RDNA 3, RDNA 3.5 or RDNA 4 GPU, and a current AMD Adrenalin driver.
Desktop, mobile, workstation and integrated GPUs use their actual gfx target automatically.
The full release covers gfx1100-1103, gfx1150-1154 and gfx1200-1201; smaller ZIPs may cover a subset.
- A DirectX 12 game without anti-cheat (OptiScaler injects a DLL; it can get an account banned). - A DirectX 12 game without anti-cheat (OptiScaler injects a DLL; it can get an account banned).
- NVIDIA DLSS files. The ZIP does not include them. Run install.bat to download official, pinned files. - NVIDIA DLSS files. The ZIP does not include them. Run install.bat to download official, pinned files.
It downloads DLSS SR 310.9.1, Ray Reconstruction 310.9.1, and extracts _nvngx.dll from the It downloads DLSS SR 310.9.1, Ray Reconstruction 310.9.1, and extracts _nvngx.dll from the
@@ -30,7 +32,9 @@ Install
folder with their original paths and SHA256 hashes. Verified downloads are cached in folder with their original paths and SHA256 hashes. Verified downloads are cached in
%LOCALAPPDATA%\d4r\cache. %LOCALAPPDATA%\d4r\cache.
Bundled isolated Python runs the installer; system Python, PowerShell and Git Bash are not required. Bundled isolated Python runs the installer; system Python, PowerShell and Git Bash are not required.
Choose N to keep RR off, or D/E to build and enable an experimental private native RR model. Super Resolution is ready after this step; no architecture selection or model build is required.
Ray Reconstruction stays off by default. To build and enable an experimental private native RR
model, run install.bat --yes --rr-preset D --enable-rr (or use E instead of D).
Automatic RR building currently requires the gfx1101-only package. Model tools run from the Automatic RR building currently requires the gfx1101-only package. Model tools run from the
external cache. Existing models remain intact; d4r.ini receives an exact external backup before external cache. Existing models remain intact; d4r.ini receives an exact external backup before
selecting the new relative model path. Game compatibility and reference quality remain unverified. selecting the new relative model path. Game compatibility and reference quality remain unverified.
@@ -52,12 +56,12 @@ Edit d4r\d4r.ini (it is commented) and restart the game. Each game has its own c
- AsyncInterop, ValidateOutput, Log: for testing and bug reports; the defaults are the tested setting. - 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; - 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 ModelDir selects your locally generated D/E model (auto: d4r\engine\rr-d). Relative paths resolve
beside d4r.ini. No model is bundled; install.bat can optionally generate and select D/E locally. beside d4r.ini. No model is bundled; install.bat --yes --rr-preset D --enable-rr can generate D locally.
fsr (experimental) answers the game's DLSS RR request with AMD's FSR Ray Regeneration denoiser, fsr (experimental) answers the game's DLSS RR request with AMD's FSR Ray Regeneration denoiser,
then DLSS Super Sampling upscales the result. AMD does not support FSR RR on RDNA3, so d4r runs then DLSS Super Sampling upscales the result. AMD does not support FSR RR on RDNA3, so d4r runs
AMD's unmodified denoiser DLL on a private vkd3d-proton device (d4r\fsr, built by d4r, LGPL; see AMD's unmodified denoiser DLL on a private vkd3d-proton device (d4r\fsr, built by d4r, LGPL; see
d4r\fsr\SOURCE.txt) that emulates the FP8 math on RDNA3. AMD's amd_fidelityfx_denoiser_dx12.dll is d4r\fsr\SOURCE.txt) that emulates the FP8 math on RDNA3. AMD's amd_fidelityfx_denoiser_dx12.dll is
not bundled: install.bat [F] downloads the pinned file (SHA256 48f1e588...), or set FsrDenoiser. not bundled: install.bat --yes --enable-fsr downloads the pinned file (SHA256 48f1e588...), or set FsrDenoiser.
FsrStabilityBias (default auto = AMD's 1; 0 is much sharper without upscaling, see d4r.ini) and FsrStabilityBias (default auto = AMD's 1; 0 is much sharper without upscaling, see d4r.ini) and
FsrDetail (0-1) tune the denoiser. FsrDetail (0-1) tune the denoiser.
The first RR start compiles AMD's shaders and can freeze the game for about a minute; they are The first RR start compiles AMD's shaders and can freeze the game for about a minute; they are
+7 -20
View File
@@ -5,32 +5,19 @@ if not exist "%~dp0d4r\model-tools\python\python.exe" (
exit /b 1 exit /b 1
) )
if not "%~1"=="" goto arguments if not "%~1"=="" goto arguments
echo d4r will download NVIDIA DLSS files and an NVIDIA driver archive. echo d4r will download the required NVIDIA DLSS files and NVIDIA driver archive.
echo Releases with the FSR backend also download AMD's Ray Regeneration denoiser (14 MB).
echo Driver archive is about 945 MiB (991 MB). It will only extract _nvngx.dll; it will not install or change drivers. echo Driver archive is about 945 MiB (991 MB). It will only extract _nvngx.dll; it will not install or change drivers.
choice /C YN /N /M "Continue? [Y/N] " choice /C YN /N /M "Continue? [Y/N] "
if errorlevel 2 exit /b 0 if errorlevel 2 exit /b 0
echo Optional Ray Reconstruction backends (disabled by default; synthetic validation only, game compatibility unverified):
echo [D]/[E] native DLSS RR model built locally from NVIDIA's RR DLL (gfx1101 only).
echo [F] experimental FSR Ray Regeneration: downloads AMD's pinned 14 MB denoiser DLL; runs it through the
echo bundled vkd3d-proton (d4r\fsr). AMD does not support FSR RR on RDNA3; d4r emulates its FP8 math.
choice /C NDEF /N /M "Enable RR? [N]one / [D] / [E] / [F]SR: "
if errorlevel 4 goto fsr
if errorlevel 3 goto modelE
if errorlevel 2 goto modelD
"%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." --yes "%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." --yes
goto result set "D4R_INSTALL_EXIT=%ERRORLEVEL%"
:modelD set "D4R_PAUSE=1"
"%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." --yes --rr-preset D --enable-rr goto report
goto result
:modelE
"%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." --yes --rr-preset E --enable-rr
goto result
:fsr
"%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." --yes --enable-fsr
goto result
:arguments :arguments
"%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." %* "%~dp0d4r\model-tools\python\python.exe" "%~dp0install-dependencies.py" --package-root "%~dp0." %*
:result
set "D4R_INSTALL_EXIT=%ERRORLEVEL%" set "D4R_INSTALL_EXIT=%ERRORLEVEL%"
:report
if not "%D4R_INSTALL_EXIT%"=="0" echo d4r install failed. Read the message above. if not "%D4R_INSTALL_EXIT%"=="0" echo d4r install failed. Read the message above.
if defined D4R_PAUSE pause
exit /b %D4R_INSTALL_EXIT% exit /b %D4R_INSTALL_EXIT%
@@ -0,0 +1,33 @@
diff --git a/libs/vkd3d-shader/dxil.c b/libs/vkd3d-shader/dxil.c
index b28b9a65..385eb87f 100644
--- a/libs/vkd3d-shader/dxil.c
+++ b/libs/vkd3d-shader/dxil.c
@@ -1459,13 +1459,9 @@ int vkd3d_shader_compile_dxil(const struct vkd3d_shader_code *dxbc,
memset(&spirv->meta, 0, sizeof(spirv->meta));
spirv->meta.hash = hash;
- /* Cannot replace mesh shaders until we have reflected the IO layout. */
- if (shader_interface_info->stage != VK_SHADER_STAGE_MESH_BIT_EXT &&
- vkd3d_shader_replace(hash, &spirv->code, &spirv->size))
- {
- spirv->meta.flags |= VKD3D_SHADER_META_FLAG_REPLACED;
- return ret;
- }
+ /* d4r: every stage is replaced late (below), after dxil-spirv has computed the shader's meta
+ * (wave size range, workgroup size, flags). Early replacement returned zeroed meta, so a replaced
+ * compute shader lost its required wave size. */
dxil_spv_begin_thread_allocator_context();
@@ -1602,9 +1598,8 @@ int vkd3d_shader_compile_dxil(const struct vkd3d_shader_code *dxbc,
goto end;
}
- /* Late replacement for mesh shaders. */
- if (shader_interface_info->stage == VK_SHADER_STAGE_MESH_BIT_EXT &&
- vkd3d_shader_replace(hash, &spirv->code, &spirv->size))
+ /* Late replacement, keeping the meta computed above. */
+ if (vkd3d_shader_replace(hash, &spirv->code, &spirv->size))
{
spirv->meta.flags |= VKD3D_SHADER_META_FLAG_REPLACED;
}
@@ -0,0 +1,22 @@
diff --git a/libs/vkd3d-shader/dxil.c b/libs/vkd3d-shader/dxil.c
--- a/libs/vkd3d-shader/dxil.c
+++ b/libs/vkd3d-shader/dxil.c
@@ -26,6 +26,8 @@
#include <inttypes.h>
#include <stdio.h>
#include <dxil_spirv_c.h>
+#include "vkd3d_platform.h"
+#include "d4r_shader_overrides.h"
static bool dxil_match_shader_visibility(enum vkd3d_shader_visibility visibility,
dxil_spv_shader_stage stage)
@@ -1599,7 +1600,8 @@
}
/* Late replacement, keeping the meta computed above. */
- if (vkd3d_shader_replace(hash, &spirv->code, &spirv->size))
+ if (vkd3d_shader_replace(hash, &spirv->code, &spirv->size) ||
+ d4r_shader_replace_builtin(hash, compiled.data, compiled.size, &spirv->code, &spirv->size))
{
spirv->meta.flags |= VKD3D_SHADER_META_FLAG_REPLACED;
}
+1 -1
View File
@@ -35,7 +35,7 @@ try {
$build = Join-Path $repo "build/native-engine-windows-$GpuArch" $build = Join-Path $repo "build/native-engine-windows-$GpuArch"
$headers = Join-Path $tools 'Vulkan-Headers-v1.4.341' $headers = Join-Path $tools 'Vulkan-Headers-v1.4.341'
$volk = Join-Path $tools 'volk' $volk = Join-Path $tools 'volk'
& $cmake --fresh -S $repo -B $build -G Ninja "-DCMAKE_MAKE_PROGRAM=$ninja" ` & $cmake --fresh -S $repo -B $build -G Ninja '-DCMAKE_BUILD_TYPE=Release' "-DCMAKE_MAKE_PROGRAM=$ninja" `
"-DCMAKE_C_COMPILER=$cCompiler" "-DCMAKE_CXX_COMPILER=$cxxCompiler" ` "-DCMAKE_C_COMPILER=$cCompiler" "-DCMAKE_CXX_COMPILER=$cxxCompiler" `
"-DD4R_HIP_ROOT=$HipRoot" "-DD4R_GPU_ARCH=$GpuArch" ` "-DD4R_HIP_ROOT=$HipRoot" "-DD4R_GPU_ARCH=$GpuArch" `
'-DD4R_BUILD_NATIVE_ENGINE=ON' "-DD4R_VULKAN_HEADERS_ROOT=$headers" "-DD4R_VOLK_ROOT=$volk" ` '-DD4R_BUILD_NATIVE_ENGINE=ON' "-DD4R_VULKAN_HEADERS_ROOT=$headers" "-DD4R_VOLK_ROOT=$volk" `
@@ -3,7 +3,7 @@ param([ValidateSet('enc0','enc1','enc2','enc3','enc4','dec5','dec4','dec3','dec2
[string]$Layer='enc1', [ValidateSet('source','cu','wgp')][string]$Mode='source', [string]$Layer='enc1', [ValidateSet('source','cu','wgp')][string]$Mode='source',
[ValidateSet('baseline','load','store','both')][string]$NonTemporal='baseline', [ValidateSet('baseline','load','store','both')][string]$NonTemporal='baseline',
[ValidateSet(0,1,2,4)][int]$PosTiles=0, [switch]$ShareValues, [switch]$PrivateValues, [ValidateSet(0,1,2,4)][int]$PosTiles=0, [switch]$ShareValues, [switch]$PrivateValues,
[switch]$PackedAccumulator, [ValidateSet('lds','ds')][string]$LaneExchange='lds', [switch]$PackedAccumulator, [switch]$F16Wmma, [ValidateSet('lds','ds','native')][string]$LaneExchange='lds',
[ValidateSet('gfx1100','gfx1101','gfx1102','gfx1103','gfx1150','gfx1151','gfx1152','gfx1153','gfx1154','gfx1200','gfx1201')] [ValidateSet('gfx1100','gfx1101','gfx1102','gfx1103','gfx1150','gfx1151','gfx1152','gfx1153','gfx1154','gfx1200','gfx1201')]
[string]$GpuArch='gfx1201', [string]$GpuArch='gfx1201',
[string]$HipRoot, [string]$OutputDirectory) [string]$HipRoot, [string]$OutputDirectory)
@@ -11,10 +11,12 @@ $ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) $repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' } 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 ($ShareValues -and $PrivateValues) { throw 'Choose shared or private V storage.' }
if ($GpuArch -notmatch '^gfx12' -and $PackedAccumulator) { if ($GpuArch -notmatch '^gfx12' -and ($PackedAccumulator -or $F16Wmma)) {
throw 'Packed accumulator experiments require gfx12.' throw 'Packed accumulator and F16 WMMA experiments require gfx12.'
} }
if ($PackedAccumulator -and $F16Wmma) { throw 'Packed accumulator storage requires F32 WMMA.' }
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)" } 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)" }
if ($F16Wmma -and !$PSBoundParameters.ContainsKey('OutputDirectory')) { $OutputDirectory+='-f16-wmma' }
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory) $OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
foreach ($baseline in @('build/native-k-gfx1201','build/windows-rdna4-therock','dist/windows-rdna4-command-list')) { 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)) { if ($OutputDirectory.StartsWith([IO.Path]::GetFullPath((Join-Path $repo $baseline)), [StringComparison]::OrdinalIgnoreCase)) {
@@ -44,8 +46,9 @@ if ($ShareValues) {
$flags+='-DPOS_VSHARE' $flags+='-DPOS_VSHARE'
} }
if ($PackedAccumulator) { $flags+='-DD4R_K_PACKED_ACC' } if ($PackedAccumulator) { $flags+='-DD4R_K_PACKED_ACC' }
if ($F16Wmma) { $flags+='-DD4R_K_F16_WMMA' }
if ($PrivateValues) { $flags+='-DD4R_K_PRIVATE_V' } if ($PrivateValues) { $flags+='-DD4R_K_PRIVATE_V' }
if ($GpuArch -match '^gfx11') { $flags+='-DD4R_WINDOWS_GFX11_LANE_EXCHANGE' } if ($GpuArch -match '^gfx11' -and $LaneExchange -ne 'native') { $flags+='-DD4R_WINDOWS_GFX11_LANE_EXCHANGE' }
if ($LaneExchange -eq 'ds') { if ($LaneExchange -eq 'ds') {
if ($GpuArch -notmatch '^gfx11') { throw 'DS lane exchange experiment requires gfx11.' } if ($GpuArch -notmatch '^gfx11') { throw 'DS lane exchange experiment requires gfx11.' }
$flags+='-DD4R_WINDOWS_GFX11_DS_EXCHANGE' $flags+='-DD4R_WINDOWS_GFX11_DS_EXCHANGE'
@@ -69,6 +72,7 @@ if ($LASTEXITCODE) { throw 'Native object metadata extraction failed.' }
mode=$Mode; nonTemporal=$NonTemporal; tokenTilesPerWave=$PosTiles; shareValues=[bool]$ShareValues; mode=$Mode; nonTemporal=$NonTemporal; tokenTilesPerWave=$PosTiles; shareValues=[bool]$ShareValues;
privateValues=[bool]$PrivateValues; privateValues=[bool]$PrivateValues;
packedAccumulator=[bool]$PackedAccumulator; laneExchange=$LaneExchange; packedAccumulator=[bool]$PackedAccumulator; laneExchange=$LaneExchange;
f16Wmma=[bool]$F16Wmma;
compiler=$compiler; arguments=$arguments; compiler=$compiler; arguments=$arguments;
sourceCommit=(git -C $repo rev-parse HEAD); workingTreeDirty=[bool](& git -C $repo status --porcelain); sourceCommit=(git -C $repo rev-parse HEAD); workingTreeDirty=[bool](& git -C $repo status --porcelain);
compilerVersion=(& $compiler --version | Out-String).Trim(); compilerVersion=(& $compiler --version | Out-String).Trim();
@@ -0,0 +1,44 @@
[CmdletBinding()]
param(
[ValidateSet('lds','ds','native')][string]$LaneExchange='lds',
[ValidateSet('gfx1100','gfx1101','gfx1102','gfx1103','gfx1200','gfx1201')][string]$GpuArch='gfx1101',
[switch]$ReferencePv,
[string]$HipRoot, [string]$OutputDirectory
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if ($GpuArch -match '^gfx12') {
if (!$PSBoundParameters.ContainsKey('LaneExchange')) { $LaneExchange='native' }
if ($LaneExchange -ne 'native') { throw 'RDNA4 experiments use native lane exchange.' }
}
if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
if (!$OutputDirectory) {
$suffix=if ($ReferencePv) { '-reference-pv' } else { '-bounded-pv' }
$OutputDirectory=Join-Path $repo "build/native-m-experiment-$GpuArch-$LaneExchange$suffix"
}
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
# Experiments never overwrite a package or an earlier candidate.
New-Item -ItemType Directory $OutputDirectory -ErrorAction Stop | Out-Null
$compiler=Join-Path $HipRoot 'lib/llvm/bin/clang++.exe'
if (!(Test-Path -LiteralPath $compiler)) { $compiler=Join-Path $HipRoot 'bin/clang++.exe' }
$flags=@('-x','hip','-std=c++17',"--offload-arch=$GpuArch",'--offload-device-only',
'--no-gpu-bundle-output','-mno-wavefrontsize64','-nogpuinc','-nogpulib','-O3',
'-DD4R_DEVICE_ONLY_MINIMAL','-DSWIN_EXACT','-DSWIN_EXACT_PV')
if ($GpuArch -match '^gfx11' -and $LaneExchange -ne 'native') { $flags+='-DD4R_WINDOWS_GFX11_LANE_EXCHANGE' }
if ($LaneExchange -eq 'ds') { $flags+='-DD4R_WINDOWS_GFX11_DS_EXCHANGE' }
if ($ReferencePv) { $flags+='-DSWIN_EXACT_PV_REFERENCE' }
$modules=@()
foreach ($source in Get-ChildItem (Join-Path $repo 'kernels/m') -Filter '*.hip') {
$object=Join-Path $OutputDirectory ($source.BaseName+'.hsaco')
& $compiler @flags $source.FullName -o $object `
> (Join-Path $OutputDirectory ($source.BaseName+'.stdout.log')) `
2> (Join-Path $OutputDirectory ($source.BaseName+'.stderr.log'))
if ($LASTEXITCODE) { throw "M compile failed: $($source.Name)" }
$modules+=@{name=$source.BaseName; sha256=(Get-FileHash -LiteralPath $object -Algorithm SHA256).Hash}
}
@{architecture=$GpuArch; laneExchange=$LaneExchange; experimental=$true;
numerics='SWIN_EXACT; SWIN_EXACT_PV'; validation='pending';
compiler=$compiler; compilerVersion=(& $compiler --version | Out-String).Trim();
flags=$flags; modules=$modules} | ConvertTo-Json -Depth 5 |
Set-Content (Join-Path $OutputDirectory 'build-info.json') -Encoding UTF8
Write-Host "Built strict M $LaneExchange candidate: $OutputDirectory. Validate before installing."
+9 -7
View File
@@ -1,11 +1,13 @@
[CmdletBinding()] [CmdletBinding()]
param([Parameter(Mandatory=$true)][string]$DlssDll, [string]$HipRoot, [string]$OutputDirectory, [switch]$HardwarePacking, [switch]$TokenLanes) param([Parameter(Mandatory=$true)][string]$DlssDll, [string]$HipRoot, [string]$OutputDirectory, [switch]$HardwarePacking, [switch]$TokenLanes,
[ValidateSet('gfx1200','gfx1201')][string]$GpuArch='gfx1201')
$ErrorActionPreference='Stop' $ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) $repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' } if (!$HipRoot) { $HipRoot=Join-Path $repo '.tools/therock-10.2.0a20260929/_rocm_sdk_core' }
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo $(if ($HardwarePacking) { 'build/native-m-fp8-hardware-pack' } else { 'build/native-m-fp8-experimental' }) } if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo $(if ($HardwarePacking) { 'build/native-m-fp8-hardware-pack' } else { 'build/native-m-fp8-experimental' }) }
if (!$PSBoundParameters.ContainsKey('OutputDirectory') -and $GpuArch -ne 'gfx1201') { $OutputDirectory += "-$GpuArch" }
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory) $OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
if ($OutputDirectory -eq [IO.Path]::GetFullPath((Join-Path $repo 'build/native-m-gfx1201'))) { if ($OutputDirectory -eq [IO.Path]::GetFullPath((Join-Path $repo "build/native-m-$GpuArch"))) {
throw 'Keep the validated FP16 baseline separate from the experimental FP8 build.' throw 'Keep the validated FP16 baseline separate from the experimental FP8 build.'
} }
$DlssDll=(Get-Item -LiteralPath $DlssDll -ErrorAction Stop).FullName $DlssDll=(Get-Item -LiteralPath $DlssDll -ErrorAction Stop).FullName
@@ -22,7 +24,7 @@ foreach ($source in Get-ChildItem -LiteralPath (Join-Path $repo 'kernels/m') -Fi
if ($TokenLanes) { $flags+='-DSWIN_TOKEN_LANES' } if ($TokenLanes) { $flags+='-DSWIN_TOKEN_LANES' }
$object=Join-Path $OutputDirectory ($source.BaseName+'.hsaco') $object=Join-Path $OutputDirectory ($source.BaseName+'.hsaco')
$ErrorActionPreference='Continue' $ErrorActionPreference='Continue'
& $compiler -x hip -std=c++17 --offload-arch=gfx1201 --offload-device-only --no-gpu-bundle-output ` & $compiler -x hip -std=c++17 "--offload-arch=$GpuArch" --offload-device-only --no-gpu-bundle-output `
-mno-wavefrontsize64 -nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL ` -mno-wavefrontsize64 -nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL `
-DSWIN_EXACT -DSWIN_EXACT_PV -DD4R_FP8_WMMA @flags $source.FullName ` -DSWIN_EXACT -DSWIN_EXACT_PV -DD4R_FP8_WMMA @flags $source.FullName `
-o $object > (Join-Path $OutputDirectory ($source.BaseName+'.build.stdout.log')) ` -o $object > (Join-Path $OutputDirectory ($source.BaseName+'.build.stdout.log')) `
@@ -30,17 +32,17 @@ foreach ($source in Get-ChildItem -LiteralPath (Join-Path $repo 'kernels/m') -Fi
$ErrorActionPreference='Stop' $ErrorActionPreference='Stop'
if ($LASTEXITCODE) { throw "Experimental FP8 compile failed: $($source.Name)" } if ($LASTEXITCODE) { throw "Experimental FP8 compile failed: $($source.Name)" }
$isa=Join-Path $OutputDirectory ($source.BaseName+'.isa.txt') $isa=Join-Path $OutputDirectory ($source.BaseName+'.isa.txt')
& $disassembler --disassemble --mcpu=gfx1201 $object > $isa & $disassembler --disassemble "--mcpu=$GpuArch" $object > $isa
if ($LASTEXITCODE -or !(Select-String -LiteralPath $isa -Pattern 'v_wmma_f32_16x16x16_fp8_fp8')) { if ($LASTEXITCODE -or !(Select-String -LiteralPath $isa -Pattern 'v_wmma_f32_16x16x16_fp8_fp8')) {
throw "Native FP8 WMMA is absent from $($source.Name); no FP16 fallback is accepted." throw "Native FP8 WMMA is absent from $($source.Name); no FP16 fallback is accepted."
} }
if ($HardwarePacking -and !(Select-String -LiteralPath $isa -Pattern 'v_cvt_pk_fp8_f32')) { throw 'Native FP8 operand packing is absent from ISA.' } if ($HardwarePacking -and !(Select-String -LiteralPath $isa -Pattern 'v_cvt_pk_fp8_f32')) { throw 'Native FP8 operand packing is absent from ISA.' }
Write-Host "Compiled gfx1201 FP8 WMMA: $($source.BaseName)" Write-Host "Compiled $GpuArch FP8 WMMA: $($source.BaseName)"
} }
if ($HardwarePacking) { if ($HardwarePacking) {
$probeDirectory=Join-Path $OutputDirectory 'packing-probe' $probeDirectory=Join-Path $OutputDirectory 'packing-probe'
New-Item -ItemType Directory -Force $probeDirectory | Out-Null New-Item -ItemType Directory -Force $probeDirectory | Out-Null
& $compiler -x hip -std=c++17 --offload-arch=gfx1201 --offload-device-only --no-gpu-bundle-output ` & $compiler -x hip -std=c++17 "--offload-arch=$GpuArch" --offload-device-only --no-gpu-bundle-output `
-mno-wavefrontsize64 -nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL ` -mno-wavefrontsize64 -nogpuinc -nogpulib -O3 -DD4R_DEVICE_ONLY_MINIMAL `
-DD4R_FP8_WMMA -DD4R_FP8_HW_PACK -DSWIN_EXACT -DSWIN_EXACT_PV ` -DD4R_FP8_WMMA -DD4R_FP8_HW_PACK -DSWIN_EXACT -DSWIN_EXACT_PV `
(Join-Path $repo 'tools/windows/fp8_pack_probe.hip') -o (Join-Path $probeDirectory 'fp8_pack_probe.hsaco') (Join-Path $repo 'tools/windows/fp8_pack_probe.hip') -o (Join-Path $probeDirectory 'fp8_pack_probe.hsaco')
@@ -48,7 +50,7 @@ if ($HardwarePacking) {
} }
& python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll & python (Join-Path $repo 'kernels/tools/kernel_manifest.py') $OutputDirectory $DlssDll
if ($LASTEXITCODE) { throw 'Experimental FP8 manifest generation failed.' } if ($LASTEXITCODE) { throw 'Experimental FP8 manifest generation failed.' }
@{architecture='gfx1201'; family='M'; experimental=$true; nativeFp8=$true; accuracy=$true; @{architecture=$GpuArch; family='M'; experimental=$true; nativeFp8=$true; accuracy=$true;
numerics='SWIN_EXACT; SWIN_EXACT_PV; FP8 WMMA for e4m3 GEMMs; exact FP16 attention'; numerics='SWIN_EXACT; SWIN_EXACT_PV; FP8 WMMA for e4m3 GEMMs; exact FP16 attention';
validation='not validated; do not install before layer/full-network checks'; tokenLanes=[bool]$TokenLanes; validation='not validated; do not install before layer/full-network checks'; tokenLanes=[bool]$TokenLanes;
compiler=$compiler; compilerVersion=(& $compiler --version | Select-Object -First 1); compiler=$compiler; compilerVersion=(& $compiler --version | Select-Object -First 1);
+5 -2
View File
@@ -6,7 +6,8 @@ param(
[string]$ZludaRoot, [string]$ZludaRoot,
[ValidateSet('cu','wgp')][string]$ShaderMode='cu', [ValidateSet('cu','wgp')][string]$ShaderMode='cu',
[string]$GpuArch='gfx1201', [string]$GpuArch='gfx1201',
[string]$OutputDirectory [string]$OutputDirectory,
[switch]$Wave64
) )
$ErrorActionPreference='Stop' $ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'gpu-target.ps1') . (Join-Path $PSScriptRoot 'gpu-target.ps1')
@@ -14,6 +15,8 @@ $ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) $repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$ZludaRoot) { $ZludaRoot=Join-Path $repo 'dist/zluda-windows-final' } if (!$ZludaRoot) { $ZludaRoot=Join-Path $repo 'dist/zluda-windows-final' }
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo "build/private-textures-$GpuArch" } if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo "build/private-textures-$GpuArch" }
$waveFlags=@()
if ($Wave64) { $waveFlags+='--wave64' }
& python (Join-Path $repo 'tools/windows/build_native_texture.py') --dlss-dll $DlssDll --hip-root $HipRoot ` & python (Join-Path $repo 'tools/windows/build_native_texture.py') --dlss-dll $DlssDll --hip-root $HipRoot `
--zluda-root $ZludaRoot --kernel $Kernel --output-directory $OutputDirectory --shader-mode $ShaderMode --gpu-arch $GpuArch --zluda-root $ZludaRoot --kernel $Kernel --output-directory $OutputDirectory --shader-mode $ShaderMode --gpu-arch $GpuArch @waveFlags
if ($LASTEXITCODE) { throw "Native texture build failed ($LASTEXITCODE); inspect the output work directory." } if ($LASTEXITCODE) { throw "Native texture build failed ($LASTEXITCODE); inspect the output work directory." }
+13 -4
View File
@@ -4,17 +4,21 @@ param(
[ValidateSet('stable','therock')][string]$RuntimeProfile='therock', [ValidateSet('stable','therock')][string]$RuntimeProfile='therock',
[string]$ZludaRoot, [string]$DlssDll, [string]$ZludaRoot, [string]$DlssDll,
[switch]$PackageGames, [switch]$PackageGames,
[switch]$KernelsOnly,
[string]$BuildRoot, [string]$OutputRoot, [string]$ArchiveDirectory [string]$BuildRoot, [string]$OutputRoot, [string]$ArchiveDirectory
) )
$ErrorActionPreference='Stop' $ErrorActionPreference='Stop'
# Load the hash function in this scope before invoking child build scripts (PS 5.1).
Import-Module Microsoft.PowerShell.Utility
. (Join-Path $PSScriptRoot 'gpu-target.ps1') . (Join-Path $PSScriptRoot 'gpu-target.ps1')
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) $repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$GpuArchitectures) { $GpuArchitectures=@(Get-D4RGpuTargets | ForEach-Object { $_.architecture }) } if (!$GpuArchitectures) { $GpuArchitectures=@(Get-D4RGpuTargets | ForEach-Object { $_.architecture }) }
foreach ($arch in $GpuArchitectures) { [void](Get-D4RGpuTargetInfo $arch) } foreach ($arch in $GpuArchitectures) { [void](Get-D4RGpuTargetInfo $arch) }
if (@($GpuArchitectures | Select-Object -Unique).Count -ne $GpuArchitectures.Count) { throw 'Duplicate GPU target.' } if (@($GpuArchitectures | Select-Object -Unique).Count -ne $GpuArchitectures.Count) { throw 'Duplicate GPU target.' }
if (!$BuildRoot) { $BuildRoot=Join-Path $repo 'build/windows-gpu-coverage' } if (!$BuildRoot) { $BuildRoot=Join-Path $repo $(if ($KernelsOnly) { 'build/windows-kernels' } else { 'build/windows-gpu-coverage' }) }
if (!$OutputRoot) { $OutputRoot=Join-Path $repo 'dist/windows-gpu-coverage' } if (!$OutputRoot) { $OutputRoot=Join-Path $repo $(if ($KernelsOnly) { 'dist/windows-kernels' } else { 'dist/windows-gpu-coverage' }) }
if (!$ZludaRoot) { $ZludaRoot=Join-Path $repo 'dist/zluda-windows-final' } if (!$ZludaRoot) { $ZludaRoot=Join-Path $repo 'dist/zluda-windows-final' }
if ($KernelsOnly -and $PackageGames) { throw 'KernelsOnly cannot package games; game packages also require host DLLs.' }
if ($ArchiveDirectory -and !$PackageGames) { throw 'ArchiveDirectory requires PackageGames.' } if ($ArchiveDirectory -and !$PackageGames) { throw 'ArchiveDirectory requires PackageGames.' }
if ($PackageGames -and (!$DlssDll -or ![IO.Path]::IsPathRooted($DlssDll) -or !(Test-Path -LiteralPath $DlssDll))) { if ($PackageGames -and (!$DlssDll -or ![IO.Path]::IsPathRooted($DlssDll) -or !(Test-Path -LiteralPath $DlssDll))) {
throw 'PackageGames requires an absolute path to your local nvngx_dlss.dll; it will not be redistributed.' throw 'PackageGames requires an absolute path to your local nvngx_dlss.dll; it will not be redistributed.'
@@ -27,8 +31,13 @@ foreach ($arch in $GpuArchitectures) {
$log=Join-Path $BuildRoot "$arch-build.log" $log=Join-Path $BuildRoot "$arch-build.log"
Write-Host "Building $arch (compile/package only; no GPU workloads). Log: $log" Write-Host "Building $arch (compile/package only; no GPU workloads). Log: $log"
& (Join-Path $PSScriptRoot 'build-windows-rdna4.ps1') -RuntimeProfile $RuntimeProfile -GpuArch $arch ` & (Join-Path $PSScriptRoot 'build-windows-rdna4.ps1') -RuntimeProfile $RuntimeProfile -GpuArch $arch `
-ZludaRoot $ZludaRoot -BuildDirectory $build -InstallDirectory $diag *> $log -ZludaRoot $ZludaRoot -BuildDirectory $build -InstallDirectory $diag -KernelsOnly:$KernelsOnly *> $log
$row=[ordered]@{architecture=$arch; compiled=$true; hardwareTestExecuted=$false; buildDirectory=$build; diagnosticRoot=$diag; log=$log} $objects=@(Get-ChildItem -LiteralPath $diag -Recurse -Filter '*.hsaco')
if ($objects.Count -ne 19) { throw "Expected 19 code objects for $arch, found $($objects.Count). See $log" }
foreach ($object in $objects) { Assert-D4RCodeObjectTarget $object.FullName $arch }
$row=[ordered]@{architecture=$arch; compiled=$true; kernelsOnly=[bool]$KernelsOnly; codeObjectCount=$objects.Count;
hardwareTestExecuted=$false; buildDirectory=$build; diagnosticRoot=$diag; log=$log;
objects=@($objects | ForEach-Object { @{path=$_.FullName; sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash} })}
if ($PackageGames) { if ($PackageGames) {
$k="$BuildRoot-native-k-$arch"; $m="$BuildRoot-native-m-$arch"; $game="$OutputRoot-game-$arch" $k="$BuildRoot-native-k-$arch"; $m="$BuildRoot-native-m-$arch"; $game="$OutputRoot-game-$arch"
& (Join-Path $PSScriptRoot 'stage-native-k.ps1') -DlssDll $DlssDll -PackageRoot $diag -GpuArch $arch -OutputDirectory $k & (Join-Path $PSScriptRoot 'stage-native-k.ps1') -DlssDll $DlssDll -PackageRoot $diag -GpuArch $arch -OutputDirectory $k
+26 -11
View File
@@ -6,7 +6,8 @@ param(
[string]$ZludaRoot, [string]$ZludaRoot,
[string]$ToolchainRoot, [string]$ToolchainRoot,
[string]$BuildDirectory, [string]$BuildDirectory,
[string]$InstallDirectory [string]$InstallDirectory,
[switch]$KernelsOnly
) )
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'gpu-target.ps1') . (Join-Path $PSScriptRoot 'gpu-target.ps1')
@@ -24,6 +25,10 @@ if ($GpuArch -ne 'gfx1201') {
if (!$PSBoundParameters.ContainsKey('BuildDirectory')) { $BuildDirectory += "-$GpuArch" } if (!$PSBoundParameters.ContainsKey('BuildDirectory')) { $BuildDirectory += "-$GpuArch" }
if (!$PSBoundParameters.ContainsKey('InstallDirectory')) { $InstallDirectory += "-$GpuArch" } if (!$PSBoundParameters.ContainsKey('InstallDirectory')) { $InstallDirectory += "-$GpuArch" }
} }
if ($KernelsOnly) {
if (!$PSBoundParameters.ContainsKey('BuildDirectory')) { $BuildDirectory=Join-Path $repo "build/windows-kernels-$GpuArch" }
if (!$PSBoundParameters.ContainsKey('InstallDirectory')) { $InstallDirectory=Join-Path $repo "dist/windows-kernels-$GpuArch" }
}
$HipRoot = [IO.Path]::GetFullPath($HipRoot).TrimEnd('\', '/') $HipRoot = [IO.Path]::GetFullPath($HipRoot).TrimEnd('\', '/')
$BuildDirectory = [IO.Path]::GetFullPath($BuildDirectory).TrimEnd('\', '/') $BuildDirectory = [IO.Path]::GetFullPath($BuildDirectory).TrimEnd('\', '/')
$InstallDirectory = [IO.Path]::GetFullPath($InstallDirectory).TrimEnd('\', '/') $InstallDirectory = [IO.Path]::GetFullPath($InstallDirectory).TrimEnd('\', '/')
@@ -43,18 +48,21 @@ if (!(Test-Path $cmake) -or !(Test-Path $ninja)) {
} }
New-Item -ItemType Directory -Force $BuildDirectory | Out-Null New-Item -ItemType Directory -Force $BuildDirectory | Out-Null
$minhook = Join-Path $repo 'external/MinHook' $minhook = Join-Path $repo 'external/MinHook'
if (!(Test-Path (Join-Path $minhook 'include/MinHook.h'))) { if (!$KernelsOnly) {
& git clone --depth 1 --branch v1.3.4 https://github.com/TsudaKageyu/minhook.git $minhook if (!(Test-Path (Join-Path $minhook 'include/MinHook.h'))) {
if ($LASTEXITCODE) { throw 'MinHook 1.3.4 source download failed.' } & git clone --depth 1 --branch v1.3.4 https://github.com/TsudaKageyu/minhook.git $minhook
} if ($LASTEXITCODE) { throw 'MinHook 1.3.4 source download failed.' }
$minhookCommit = (& git -C $minhook rev-parse HEAD).Trim() }
if ($minhookCommit -ne 'c3fcafdc10146beb5919319d0683e44e3c30d537') { $minhookCommit = (& git -C $minhook rev-parse HEAD).Trim()
throw "Expected audited MinHook 1.3.4 source, found $minhookCommit" if ($minhookCommit -ne 'c3fcafdc10146beb5919319d0683e44e3c30d537') {
throw "Expected audited MinHook 1.3.4 source, found $minhookCommit"
}
} }
$originalPath = $env:PATH $originalPath = $env:PATH
try { try {
$configure = @('--fresh', '-S', $repo, '-B', $BuildDirectory, '-G', 'Ninja', '-DCMAKE_BUILD_TYPE=RelWithDebInfo', $configure = @('--fresh', '-S', $repo, '-B', $BuildDirectory, '-G', 'Ninja', '-DCMAKE_BUILD_TYPE=RelWithDebInfo',
"-DCMAKE_MAKE_PROGRAM=$ninja", "-DD4R_HIP_ROOT=$HipRoot", "-DD4R_GPU_ARCH=$GpuArch", "-DCMAKE_INSTALL_PREFIX=$InstallDirectory") "-DCMAKE_MAKE_PROGRAM=$ninja", "-DD4R_HIP_ROOT=$HipRoot", "-DD4R_GPU_ARCH=$GpuArch", "-DCMAKE_INSTALL_PREFIX=$InstallDirectory")
$configure += "-DD4R_KERNELS_ONLY=$([int][bool]$KernelsOnly)"
if ($ToolchainRoot) { if ($ToolchainRoot) {
$compiler = Join-Path $ToolchainRoot 'bin/x86_64-w64-mingw32-clang++.exe' $compiler = Join-Path $ToolchainRoot 'bin/x86_64-w64-mingw32-clang++.exe'
if (!(Test-Path $compiler)) { throw "Compiler missing: $compiler" } if (!(Test-Path $compiler)) { throw "Compiler missing: $compiler" }
@@ -68,12 +76,19 @@ try {
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "CMake configure exit=$LASTEXITCODE" } if ($LASTEXITCODE) { throw "CMake configure exit=$LASTEXITCODE" }
$ErrorActionPreference = 'Continue' $ErrorActionPreference = 'Continue'
& $cmake --build $BuildDirectory --parallel 4 2>&1 | Tee-Object (Join-Path $BuildDirectory 'build.log') $buildArgs=@('--build', $BuildDirectory, '--parallel', '4')
if ($KernelsOnly) { $buildArgs+=@('--target', 'd4r_native_compile', 'd4r_probe_code_object', 'd4r_pixel_code_object', 'd4r_wmma_code_object') }
& $cmake @buildArgs 2>&1 | Tee-Object (Join-Path $BuildDirectory 'build.log')
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "Build exit=$LASTEXITCODE" } if ($LASTEXITCODE) { throw "Build exit=$LASTEXITCODE" }
$ErrorActionPreference = 'Continue' $ErrorActionPreference = 'Continue'
& $cmake --install $BuildDirectory 2>&1 | Tee-Object (Join-Path $BuildDirectory 'install.log') $installArgs=@('--install', $BuildDirectory)
if ($KernelsOnly) { $installArgs+=@('--component', 'kernels') }
& $cmake @installArgs 2>&1 | Tee-Object (Join-Path $BuildDirectory 'install.log')
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
if ($LASTEXITCODE) { throw "Install exit=$LASTEXITCODE" } if ($LASTEXITCODE) { throw "Install exit=$LASTEXITCODE" }
Write-Host "Built diagnostics: $InstallDirectory" foreach ($object in Get-ChildItem -LiteralPath $BuildDirectory -Filter '*.hsaco') {
Assert-D4RCodeObjectTarget $object.FullName $GpuArch
}
Write-Host "Built $GpuArch artifacts: $InstallDirectory"
} finally { $env:PATH = $originalPath } } finally { $env:PATH = $originalPath }
@@ -15,6 +15,58 @@ spec.loader.exec_module(installer)
class InstallTests(unittest.TestCase): class InstallTests(unittest.TestCase):
def test_download_prompt_and_default_install_for_every_gpu(self):
targets = json.loads((REPO / 'tools/windows/gpu-targets.json').read_text())['targets']
(self.runtime / 'd4r.ini').write_text('[RayReconstruction]\nBackend = off\n')
(self.runtime / 'dll-pins.txt').write_text('')
for target in targets:
(self.runtime / 'package.json').write_text(json.dumps({'targets': {target['architecture']: {}}}))
for answer, proceeds in (('y', True), ('Y', True), ('', True), ('n', False), (' N ', False)):
with self.subTest(target=target['architecture'], answer=answer), \
patch('sys.argv', ['install-dependencies.py', '--package-root', str(self.runtime),
'--cache-root', str(self.root / 'cache'),
'--backup-root', str(self.backups)]), \
patch('builtins.input', return_value=answer), \
patch('builtins.print'), patch.object(installer, 'PINS', {}), \
patch.object(installer, 'collect', return_value=[]) as collect, \
patch.object(installer, 'transaction', return_value={}) as transaction, \
patch.object(installer, 'absolutize_library_paths'), \
patch.object(installer, 'install_model') as model:
installer.main()
self.assertEqual(collect.called, proceeds)
self.assertEqual(transaction.called, proceeds)
model.assert_not_called()
def test_yes_installs_without_prompt_or_model(self):
(self.runtime / 'd4r.ini').write_text('[RayReconstruction]\nBackend = off\n')
(self.runtime / 'dll-pins.txt').write_text('')
with patch('sys.argv', ['install-dependencies.py', '--package-root', str(self.runtime), '--yes',
'--cache-root', str(self.root / 'cache'), '--backup-root', str(self.backups)]), \
patch('builtins.input', side_effect=AssertionError('Unexpected prompt')), \
patch('builtins.print'), patch.object(installer, 'PINS', {}), \
patch.object(installer, 'collect', return_value=[]), \
patch.object(installer, 'transaction', return_value={}) as transaction, \
patch.object(installer, 'absolutize_library_paths'), \
patch.object(installer, 'install_model') as model:
installer.main()
transaction.assert_called_once()
model.assert_not_called()
def test_native_fsr_requires_explicit_rdna4_only_targets(self):
with tempfile.TemporaryDirectory() as directory:
runtime = Path(directory)
self.assertFalse(installer.native_fsr_package(runtime))
metadata = runtime / 'package.json'
for targets, wanted in (({}, False), ({'gfx1201': {}}, True),
({'gfx1200': {}, 'gfx1201': {}}, True),
({'gfx1101': {}, 'gfx1201': {}}, False)):
metadata.write_text(json.dumps({'targets': targets}))
self.assertEqual(installer.native_fsr_package(runtime), wanted)
(runtime / 'gpu-target.json').write_text(json.dumps({'architecture': 'gfx1101'}))
self.assertFalse(installer.native_fsr_package(runtime))
(runtime / 'gpu-target.json').write_text(json.dumps({'architecture': 'gfx1201'}))
self.assertFalse(installer.native_fsr_package(runtime)) # mixed package still includes RDNA3
def setUp(self): def setUp(self):
self.work = tempfile.TemporaryDirectory(prefix='d4r-installer-test-') self.work = tempfile.TemporaryDirectory(prefix='d4r-installer-test-')
self.addCleanup(self.work.cleanup) self.addCleanup(self.work.cleanup)
+30 -1
View File
@@ -1,9 +1,38 @@
import sys import sys
import json
import tempfile
from pathlib import Path from pathlib import Path
import unittest import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'tools/windows')) sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'tools/windows'))
from deploy_rr_model import select_native from deploy_rr_model import select_native, model_arithmetic
class ModelTargetTests(unittest.TestCase):
def test_rdna4_targets_require_exact_match(self):
with tempfile.TemporaryDirectory() as directory:
runtime = Path(directory)
for target in ('gfx1200', 'gfx1201'):
identity = {'preset': 'D', 'architecture': target, 'arithmetic': 'exact-gfx12'}
(runtime / 'gpu-target.json').write_text(json.dumps({'architecture': target}))
self.assertEqual(model_arithmetic(identity, runtime), 'gfx12')
identity['architecture'] = 'gfx1201' if target == 'gfx1200' else 'gfx1200'
with self.assertRaisesRegex(RuntimeError, 'does not match'):
model_arithmetic(identity, runtime)
def test_rdna4_requires_target_metadata(self):
with tempfile.TemporaryDirectory() as directory:
identity = {'preset': 'E', 'architecture': 'gfx1201', 'arithmetic': 'exact-gfx12'}
with self.assertRaisesRegex(RuntimeError, 'gpu-target.json'):
model_arithmetic(identity, Path(directory))
def test_wrong_arithmetic_rejected_and_legacy_preserved(self):
with tempfile.TemporaryDirectory() as directory:
identity = {'preset': 'E', 'architecture': 'gfx1101', 'arithmetic': 'exact-gfx11'}
self.assertEqual(model_arithmetic(identity, Path(directory)), 'gfx11')
identity['architecture'] = 'gfx1201'
with self.assertRaisesRegex(RuntimeError, 'identity'):
model_arithmetic(identity, Path(directory))
class ConfigSelectionTests(unittest.TestCase): class ConfigSelectionTests(unittest.TestCase):
+43
View File
@@ -0,0 +1,43 @@
import struct
import sys
import tempfile
from pathlib import Path
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'tools/windows'))
from build_rr_model import require_fp8_matrices
class NativeFp8Tests(unittest.TestCase):
def check_module(self, capabilities, trailing=()):
words = [0x07230203, 0x00010600, 0, 1, 0]
for capability in capabilities:
words += [(2 << 16) | 17, capability]
words.extend(trailing)
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'stage.spv'
path.write_bytes(struct.pack('<' + 'I' * len(words), *words))
require_fp8_matrices(path)
def test_native_fp8(self):
self.check_module((4212, 4213, 6022))
def test_fp16_fallback_rejected(self):
with self.assertRaisesRegex(RuntimeError, 'Native FP8'):
self.check_module((9, 6022))
def test_scalar_fp8_rejected(self):
with self.assertRaisesRegex(RuntimeError, 'Native FP8'):
self.check_module((4212, 6022))
def test_truncated_instruction_rejected(self):
with self.assertRaisesRegex(RuntimeError, 'instruction'):
self.check_module((4212, 4213, 6022), ((3 << 16) | 17, 9))
def test_zero_length_instruction_rejected(self):
with self.assertRaisesRegex(RuntimeError, 'instruction'):
self.check_module((4212, 4213, 6022), (17,))
if __name__ == '__main__':
unittest.main()
+777
View File
@@ -0,0 +1,777 @@
// Diagnostic per-dispatch GPU timing of AMD's denoiser (D4R_FSR_RR_DISPATCH_PROFILE=1, off by default).
//
// While ffxDispatch records into the game's list, the list's Dispatch/ExecuteIndirect entries (vkd3d-proton's
// shared command-list vtable) are wrapped to put a timestamp before every dispatch; the device's compute PSO
// creation is wrapped to tag each pipeline with vkd3d's shader hash (FNV-1 64 of the DXBC blob, the name
// VKD3D_SHADER_DUMP_PATH and VKD3D_SHADER_OVERRIDE use). Stamp i..i+1 is the time from the end of the work
// before dispatch i to the end of dispatch i (bottom-of-pipe), which is dispatch i's cost when the denoiser
// separates its passes with barriers. Wrappers forward unchanged outside the recording thread's window.
#pragma once
#include <d3d12.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <mutex>
#include <unordered_map>
#include <vector>
namespace fsr_rr_dp
{
constexpr UINT kSlots = 4;
constexpr UINT kStampsPerSlot = 2048;
using DispatchFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, UINT, UINT, UINT);
using ExecuteIndirectFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, ID3D12CommandSignature*, UINT,
ID3D12Resource*, UINT64, ID3D12Resource*, UINT64);
using ResourceBarrierFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, UINT, const D3D12_RESOURCE_BARRIER*);
using SetRootCbvFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, UINT, D3D12_GPU_VIRTUAL_ADDRESS);
using SetPipelineStateFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, ID3D12PipelineState*);
using CreateComputeFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_COMPUTE_PIPELINE_STATE_DESC*, REFIID,
void**);
using CreateStreamFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_PIPELINE_STATE_STREAM_DESC*, REFIID,
void**);
struct Record
{
uint64_t hash;
UINT x, y, z;
bool indirect;
uint32_t barriers, uavBarriers; // recorded by the denoiser between the previous dispatch and this one
};
struct Stat
{
uint64_t hash = 0;
UINT x = 0, y = 0, z = 0;
bool indirect = false;
uint32_t barriers = 0, uavBarriers = 0;
double sum = 0;
uint32_t count = 0;
};
struct State
{
std::mutex mutex;
std::unordered_map<const void*, uint64_t> psoHash;
DispatchFn dispatch = nullptr;
ExecuteIndirectFn executeIndirect = nullptr;
SetPipelineStateFn setPipelineState = nullptr;
ResourceBarrierFn resourceBarrier = nullptr;
SetRootCbvFn setRootCbv = nullptr;
uint32_t barriersSinceDispatch = 0, uavBarriersSinceDispatch = 0;
CreateComputeFn createCompute = nullptr;
CreateStreamFn createStream = nullptr;
bool listHooked = false, deviceHooked = false, failed = false;
ID3D12QueryHeap* heap = nullptr;
ID3D12Resource* readback = nullptr;
volatile uint64_t* values = nullptr;
double ticksPerMs = 0;
std::vector<Record> records[kSlots];
UINT used[kSlots] = {};
std::map<UINT, Stat> stats; // by dispatch index within the frame
uint32_t frames = 0;
uint64_t lastHash = 0;
};
inline State& state()
{
static State s;
return s;
}
thread_local ID3D12GraphicsCommandList* t_list = nullptr;
thread_local UINT t_slot = 0;
thread_local uint64_t t_pso = 0;
thread_local D3D12_GPU_VIRTUAL_ADDRESS t_cbv = 0;
inline void snapshot_after_dispatch(ID3D12GraphicsCommandList* list);
inline uint64_t fnv1(const void* data, size_t size)
{
uint64_t h = 0xcbf29ce484222325ull;
const auto* p = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < size; ++i)
h = (h * 0x100000001b3ull) ^ p[i];
return h;
}
inline bool patch(void** slot, void* hook, void** original)
{
if (*slot == hook)
return true;
DWORD old = 0;
if (!VirtualProtect(slot, sizeof(void*), PAGE_READWRITE, &old))
return false;
*original = *slot;
*slot = hook;
VirtualProtect(slot, sizeof(void*), old, &old);
return true;
}
inline void stamp(ID3D12GraphicsCommandList* list, uint64_t hash, UINT x, UINT y, UINT z, bool indirect)
{
State& s = state();
UINT& used = s.used[t_slot];
if (used + 1 >= kStampsPerSlot)
return;
list->EndQuery(s.heap, D3D12_QUERY_TYPE_TIMESTAMP, t_slot * kStampsPerSlot + used);
s.records[t_slot].push_back({hash, x, y, z, indirect, s.barriersSinceDispatch, s.uavBarriersSinceDispatch});
s.barriersSinceDispatch = s.uavBarriersSinceDispatch = 0;
++used;
}
static void STDMETHODCALLTYPE hook_set_pipeline_state(ID3D12GraphicsCommandList* list, ID3D12PipelineState* pso)
{
State& s = state();
if (t_list == list)
{
std::lock_guard<std::mutex> lock(s.mutex);
auto found = s.psoHash.find(pso);
t_pso = found != s.psoHash.end() ? found->second : 0;
}
s.setPipelineState(list, pso);
}
inline bool serialize()
{
static const bool on = env_uint("D4R_FSR_RR_DP_SERIALIZE", 0) != 0;
return on;
}
static void STDMETHODCALLTYPE hook_resource_barrier(ID3D12GraphicsCommandList* list, UINT count,
const D3D12_RESOURCE_BARRIER* barriers)
{
State& s = state();
if (t_list == list)
for (UINT i = 0; i < count; ++i)
{
++s.barriersSinceDispatch;
if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_UAV)
++s.uavBarriersSinceDispatch;
}
s.resourceBarrier(list, count, barriers);
}
// Diagnostic: a full UAV barrier before every dispatch of the denoiser (tests for missing barriers).
inline void serialize_barrier(ID3D12GraphicsCommandList* list)
{
if (!serialize())
return;
D3D12_RESOURCE_BARRIER uav = {};
uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
state().resourceBarrier(list, 1, &uav);
}
static void STDMETHODCALLTYPE hook_set_root_cbv(ID3D12GraphicsCommandList* list, UINT index,
D3D12_GPU_VIRTUAL_ADDRESS va)
{
if (t_list == list)
t_cbv = va;
state().setRootCbv(list, index, va);
}
static void STDMETHODCALLTYPE hook_dispatch(ID3D12GraphicsCommandList* list, UINT x, UINT y, UINT z)
{
State& s = state();
if (t_list == list)
{
serialize_barrier(list);
stamp(list, t_pso, x, y, z, false);
}
s.dispatch(list, x, y, z);
if (t_list == list)
snapshot_after_dispatch(list);
}
static void STDMETHODCALLTYPE hook_execute_indirect(ID3D12GraphicsCommandList* list, ID3D12CommandSignature* sig,
UINT maxCount, ID3D12Resource* args, UINT64 argsOffset,
ID3D12Resource* count, UINT64 countOffset)
{
State& s = state();
if (t_list == list)
{
serialize_barrier(list);
stamp(list, t_pso, maxCount, 0, 0, true);
}
s.executeIndirect(list, sig, maxCount, args, argsOffset, count, countOffset);
if (t_list == list)
snapshot_after_dispatch(list);
}
static HRESULT STDMETHODCALLTYPE hook_create_compute(ID3D12Device* device, const D3D12_COMPUTE_PIPELINE_STATE_DESC* desc,
REFIID riid, void** pso)
{
State& s = state();
const HRESULT hr = s.createCompute(device, desc, riid, pso);
if (SUCCEEDED(hr) && pso != nullptr && *pso != nullptr && desc != nullptr && desc->CS.pShaderBytecode != nullptr)
{
const uint64_t hash = fnv1(desc->CS.pShaderBytecode, desc->CS.BytecodeLength);
std::lock_guard<std::mutex> lock(s.mutex);
s.psoHash[*pso] = hash;
}
return hr;
}
// The CS bytecode of a pipeline state stream (D3DX12-style subobjects, each aligned to a pointer).
inline const D3D12_SHADER_BYTECODE* stream_cs(const D3D12_PIPELINE_STATE_STREAM_DESC* desc)
{
auto* p = static_cast<const uint8_t*>(desc->pPipelineStateSubobjectStream);
const uint8_t* end = p + desc->SizeInBytes;
while (p + sizeof(UINT) <= end)
{
const UINT type = *reinterpret_cast<const UINT*>(p);
size_t size;
switch (type)
{
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE: size = 16; break;
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS:
return reinterpret_cast<const D3D12_SHADER_BYTECODE*>(p + 8);
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CACHED_PSO: size = 24; break;
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_NODE_MASK:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_FLAGS: size = 8; break;
default: return nullptr;
}
p += size;
}
return nullptr;
}
static HRESULT STDMETHODCALLTYPE hook_create_stream(ID3D12Device* device, const D3D12_PIPELINE_STATE_STREAM_DESC* desc,
REFIID riid, void** pso)
{
State& s = state();
const HRESULT hr = s.createStream(device, desc, riid, pso);
const D3D12_SHADER_BYTECODE* cs = desc != nullptr ? stream_cs(desc) : nullptr;
if (SUCCEEDED(hr) && pso != nullptr && *pso != nullptr && cs != nullptr && cs->pShaderBytecode != nullptr)
{
const uint64_t hash = fnv1(cs->pShaderBytecode, cs->BytecodeLength);
std::lock_guard<std::mutex> lock(s.mutex);
s.psoHash[*pso] = hash;
}
return hr;
}
// D4R_FSR_RR_DP_SNAPSHOT=k1,k2,...: on frame D4R_FSR_RR_DP_SNAPSHOT_FRAME (default 1), copy every UAV buffer the
// denoiser created after its dispatch k (0-based), to <D4R_FSR_RR_DUMP_DIR>/snap_<k>_<buffer>.raw at teardown.
using CreateCommittedFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_HEAP_PROPERTIES*, D3D12_HEAP_FLAGS,
const D3D12_RESOURCE_DESC*, D3D12_RESOURCE_STATES,
const D3D12_CLEAR_VALUE*, REFIID, void**);
using CreatePlacedFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, ID3D12Heap*, UINT64, const D3D12_RESOURCE_DESC*,
D3D12_RESOURCE_STATES, const D3D12_CLEAR_VALUE*, REFIID, void**);
using CreateCommitted1Fn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_HEAP_PROPERTIES*, D3D12_HEAP_FLAGS,
const D3D12_RESOURCE_DESC*, D3D12_RESOURCE_STATES,
const D3D12_CLEAR_VALUE*, ID3D12ProtectedResourceSession*,
REFIID, void**);
struct Created
{
ID3D12Resource* resource;
D3D12_RESOURCE_DESC desc;
const char* how;
};
inline std::vector<Created>& created()
{
static std::vector<Created> c;
return c;
}
thread_local bool t_creating = false;
inline CreateCommittedFn& orig_committed() { static CreateCommittedFn f = nullptr; return f; }
inline CreatePlacedFn& orig_placed() { static CreatePlacedFn f = nullptr; return f; }
inline CreateCommitted1Fn& orig_committed1() { static CreateCommitted1Fn f = nullptr; return f; }
inline void note_created(HRESULT hr, const D3D12_RESOURCE_DESC* desc, void** out, const char* how)
{
if (!t_creating || FAILED(hr) || out == nullptr || *out == nullptr || desc == nullptr)
return;
// Only ID3D12Resource-compatible pointers are recorded; every resource interface starts with ID3D12Resource.
created().push_back({static_cast<ID3D12Resource*>(*out), *desc, how});
logf("D4R_FSR_RR_DP resource %zu (%s): dim %d %llux%ux%u format %d flags 0x%x", created().size() - 1, how,
desc->Dimension, static_cast<unsigned long long>(desc->Width), desc->Height, desc->DepthOrArraySize,
desc->Format, desc->Flags);
}
static HRESULT STDMETHODCALLTYPE hook_committed(ID3D12Device* d, const D3D12_HEAP_PROPERTIES* hp, D3D12_HEAP_FLAGS hf,
const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES st,
const D3D12_CLEAR_VALUE* cv, REFIID riid, void** out)
{
const HRESULT hr = orig_committed()(d, hp, hf, desc, st, cv, riid, out);
note_created(hr, desc, out, "committed");
return hr;
}
static HRESULT STDMETHODCALLTYPE hook_placed(ID3D12Device* d, ID3D12Heap* heap, UINT64 offset,
const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES st,
const D3D12_CLEAR_VALUE* cv, REFIID riid, void** out)
{
const HRESULT hr = orig_placed()(d, heap, offset, desc, st, cv, riid, out);
note_created(hr, desc, out, "placed");
return hr;
}
static HRESULT STDMETHODCALLTYPE hook_committed1(ID3D12Device* d, const D3D12_HEAP_PROPERTIES* hp, D3D12_HEAP_FLAGS hf,
const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES st,
const D3D12_CLEAR_VALUE* cv, ID3D12ProtectedResourceSession* ps,
REFIID riid, void** out)
{
const HRESULT hr = orig_committed1()(d, hp, hf, desc, st, cv, ps, riid, out);
note_created(hr, desc, out, "committed1");
return hr;
}
struct Snapshot
{
UINT dispatch;
std::vector<std::pair<size_t, ID3D12Resource*>> copies; // created() index, readback buffer
};
inline std::vector<Snapshot>& snapshots()
{
static std::vector<Snapshot> s;
return s;
}
inline const std::vector<UINT>& snapshot_points()
{
static const std::vector<UINT> points = [] {
std::vector<UINT> p;
const std::string list = env_string("D4R_FSR_RR_DP_SNAPSHOT");
size_t at = 0;
while (at < list.size())
{
const size_t comma = list.find(',', at);
p.push_back(static_cast<UINT>(std::stoul(list.substr(at, comma - at))));
at = comma == std::string::npos ? list.size() : comma + 1;
}
return p;
}();
return points;
}
thread_local uint32_t t_frame = 0;
thread_local UINT t_dispatchIndex = 0;
thread_local ID3D12Device* t_device = nullptr;
inline bool enabled()
{
static const bool on = env_uint("D4R_FSR_RR_DISPATCH_PROFILE", 0) != 0;
return on;
}
// Before the denoiser's context is created: tag the PSOs it builds.
inline void hook_device(ID3D12Device* device)
{
State& s = state();
if (!enabled() || s.deviceHooked)
return;
void** vtable = *reinterpret_cast<void***>(device);
ID3D12Device2* device2 = nullptr;
bool ok = patch(&vtable[11], reinterpret_cast<void*>(&hook_create_compute), reinterpret_cast<void**>(&s.createCompute));
if (SUCCEEDED(device->QueryInterface(__uuidof(ID3D12Device2), reinterpret_cast<void**>(&device2))))
{
void** vtable2 = *reinterpret_cast<void***>(device2);
ok = ok && patch(&vtable2[47], reinterpret_cast<void*>(&hook_create_stream), reinterpret_cast<void**>(&s.createStream));
device2->Release();
}
if (!snapshot_points().empty())
{
ID3D12Device4* device4 = nullptr;
ok = ok && patch(&vtable[27], reinterpret_cast<void*>(&hook_committed), reinterpret_cast<void**>(&orig_committed())) &&
patch(&vtable[29], reinterpret_cast<void*>(&hook_placed), reinterpret_cast<void**>(&orig_placed()));
if (SUCCEEDED(device->QueryInterface(__uuidof(ID3D12Device4), reinterpret_cast<void**>(&device4))))
{
void** vtable4 = *reinterpret_cast<void***>(device4);
ok = ok && patch(&vtable4[53], reinterpret_cast<void*>(&hook_committed1),
reinterpret_cast<void**>(&orig_committed1()));
device4->Release();
}
}
s.deviceHooked = ok;
logf("D4R_FSR_RR_DISPATCH_PROFILE: PSO creation hooks %s", ok ? "installed" : "FAILED");
}
inline bool ensure(ID3D12Device* device, ID3D12GraphicsCommandList* list)
{
State& s = state();
if (s.failed)
return false;
if (!s.listHooked)
{
void** vtable = *reinterpret_cast<void***>(list);
const bool ok =
patch(&vtable[14], reinterpret_cast<void*>(&hook_dispatch), reinterpret_cast<void**>(&s.dispatch)) &&
patch(&vtable[25], reinterpret_cast<void*>(&hook_set_pipeline_state),
reinterpret_cast<void**>(&s.setPipelineState)) &&
patch(&vtable[59], reinterpret_cast<void*>(&hook_execute_indirect),
reinterpret_cast<void**>(&s.executeIndirect)) &&
patch(&vtable[26], reinterpret_cast<void*>(&hook_resource_barrier),
reinterpret_cast<void**>(&s.resourceBarrier)) &&
patch(&vtable[37], reinterpret_cast<void*>(&hook_set_root_cbv), reinterpret_cast<void**>(&s.setRootCbv));
if (!ok)
{
s.failed = true;
logf("D4R_FSR_RR_DISPATCH_PROFILE: command-list hooks FAILED");
return false;
}
s.listHooked = true;
}
if (s.heap == nullptr)
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ID3D12CommandQueue* queue = nullptr;
UINT64 frequency = 0;
if (SUCCEEDED(device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&queue))))
{
queue->GetTimestampFrequency(&frequency);
queue->Release();
}
D3D12_QUERY_HEAP_DESC heapDesc = {};
heapDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
heapDesc.Count = kSlots * kStampsPerSlot;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = 8ull * kSlots * kStampsPerSlot;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
void* mapped = nullptr;
if (frequency == 0 ||
FAILED(device->CreateQueryHeap(&heapDesc, __uuidof(ID3D12QueryHeap), reinterpret_cast<void**>(&s.heap))) ||
FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource),
reinterpret_cast<void**>(&s.readback))) ||
FAILED(s.readback->Map(0, nullptr, &mapped)))
{
s.failed = true;
logf("D4R_FSR_RR_DISPATCH_PROFILE: query heap FAILED");
return false;
}
s.values = static_cast<volatile uint64_t*>(mapped);
for (UINT i = 0; i < kSlots * kStampsPerSlot; ++i)
s.values[i] = 0;
s.ticksPerMs = static_cast<double>(frequency) / 1000.0;
}
return true;
}
inline void log_stats()
{
State& s = state();
double total = 0;
for (const auto& [index, stat] : s.stats)
total += stat.sum / std::max(1u, stat.count);
logf("D4R_FSR_RR_DISPATCH_PROFILE: %u frames, %zu dispatches/frame, %.3f ms GPU/frame (mean)", s.frames,
s.stats.size(), total);
for (const auto& [index, stat] : s.stats)
logf("D4R_FSR_RR_DISPATCH %4u %016llx %s %5u %5u %5u %.4f ms (n=%u) barriers %u uav %u", index,
static_cast<unsigned long long>(stat.hash), stat.indirect ? "ind" : "dir", stat.x, stat.y, stat.z,
stat.sum / std::max(1u, stat.count), stat.count, stat.barriers, stat.uavBarriers);
std::map<uint64_t, std::pair<double, uint32_t>> byHash;
for (const auto& [index, stat] : s.stats)
{
auto& entry = byHash[stat.hash];
entry.first += stat.sum / std::max(1u, stat.count);
++entry.second;
}
std::vector<std::pair<double, uint64_t>> ranked;
for (const auto& [hash, entry] : byHash)
ranked.push_back({entry.first, hash});
std::sort(ranked.rbegin(), ranked.rend());
for (const auto& [ms, hash] : ranked)
logf("D4R_FSR_RR_SHADER %016llx %.4f ms/frame over %u dispatches (%.1f%%)", static_cast<unsigned long long>(hash),
ms, byHash[hash].second, total > 0 ? 100.0 * ms / total : 0.0);
}
// Around the denoiser's ffxDispatch. frame selects a slot; the slot recorded kSlots frames ago has retired.
inline void begin(ID3D12Device* device, ID3D12GraphicsCommandList* list, uint32_t frame)
{
if (!enabled() || !ensure(device, list))
return;
State& s = state();
const UINT slot = frame % kSlots;
volatile uint64_t* values = s.values + slot * kStampsPerSlot;
const UINT used = s.used[slot];
if (used > 1 && values[0] != 0 && values[used - 1] >= values[0])
{
// Frames 1-5 carry the cold start; the stats keep warm frames only.
if (frame > 5 + kSlots)
{
for (UINT i = 0; i + 1 < used; ++i)
{
Stat& stat = s.stats[i];
const Record& r = s.records[slot][i];
stat.hash = r.hash, stat.x = r.x, stat.y = r.y, stat.z = r.z, stat.indirect = r.indirect;
stat.barriers = r.barriers, stat.uavBarriers = r.uavBarriers;
stat.sum += static_cast<double>(values[i + 1] - values[i]) / s.ticksPerMs;
++stat.count;
}
if (++s.frames % profile_log_every() == 0)
log_stats();
}
}
for (UINT i = 0; i < kStampsPerSlot; ++i)
values[i] = 0;
s.records[slot].clear();
s.used[slot] = 0;
t_slot = slot;
t_pso = 0;
t_list = list;
t_frame = frame;
t_dispatchIndex = 0;
t_device = device;
s.barriersSinceDispatch = s.uavBarriersSinceDispatch = 0;
stamp(list, 0, 0, 0, 0, false); // the start
}
inline void end(ID3D12GraphicsCommandList* list)
{
State& s = state();
if (t_list != list)
return;
t_list = nullptr;
stamp(list, 0, 0, 0, 0, false); // after the last dispatch
list->ResolveQueryData(s.heap, D3D12_QUERY_TYPE_TIMESTAMP, t_slot * kStampsPerSlot, s.used[t_slot], s.readback,
8ull * t_slot * kStampsPerSlot);
}
// D4R_FSR_RR_DUMP_DIR=<Windows dir>: also copy the denoiser's two outputs of every frame (RGBA16F, rows packed)
// to <dir>/denoised_<frame>_<diffuse|specular>.raw, for checking replacement shaders against the original.
// The copies are written once the frame has retired (4 frames later, or at teardown).
struct Dump
{
uint32_t frame = 0;
ID3D12Resource* buffer = nullptr;
UINT count = 0;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint[8] = {};
UINT rows[8] = {};
UINT64 rowBytes[8] = {};
const char* names[8] = {};
bool pending = false;
};
inline std::vector<Dump>& dumps(int set)
{
static std::vector<Dump> d[2] = {std::vector<Dump>(8), std::vector<Dump>(8)};
return d[set];
}
inline void dump_write(Dump& d)
{
if (!d.pending)
return;
d.pending = false;
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
void* mapped = nullptr;
if (FAILED(d.buffer->Map(0, nullptr, &mapped)))
return;
for (UINT i = 0; i < d.count; ++i)
{
char name[512];
std::snprintf(name, sizeof(name), "%s\\%s_%03u.raw", dir.c_str(), d.names[i], d.frame);
FILE* f = std::fopen(name, "wb");
if (f == nullptr)
continue;
const auto* base = static_cast<const uint8_t*>(mapped) + d.footprint[i].Offset;
for (UINT row = 0; row < d.rows[i]; ++row)
std::fwrite(base + static_cast<size_t>(row) * d.footprint[i].Footprint.RowPitch, 1, d.rowBytes[i], f);
std::fclose(f);
}
d.buffer->Unmap(0, nullptr);
}
// set 0/1: independent rings (e.g. the denoiser's inputs and its outputs). Textures are in `state` and return to it.
inline void dump_record(int set, ID3D12Device* device, ID3D12GraphicsCommandList* list, uint32_t frame, UINT count,
ID3D12Resource* const* textures, const char* const* names, const D3D12_RESOURCE_STATES state)
{
static const bool on = !env_string("D4R_FSR_RR_DUMP_DIR").empty();
if (!on || count > 8)
return;
Dump& d = dumps(set)[frame % dumps(set).size()];
dump_write(d); // the frame that used this slot has retired
if (d.buffer == nullptr)
{
UINT64 offset = 0;
for (UINT i = 0; i < count; ++i)
{
D3D12_RESOURCE_DESC desc = textures[i]->GetDesc();
UINT64 total = 0;
device->GetCopyableFootprints(&desc, 0, 1, offset, &d.footprint[i], &d.rows[i], &d.rowBytes[i], &total);
offset = (offset + total + 65535) & ~65535ull;
}
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC buffer = {};
buffer.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
buffer.Width = offset;
buffer.Height = 1;
buffer.DepthOrArraySize = 1;
buffer.MipLevels = 1;
buffer.SampleDesc.Count = 1;
buffer.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
if (FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &buffer, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&d.buffer))))
{
d.buffer = nullptr;
return;
}
}
d.count = count;
D3D12_RESOURCE_BARRIER barriers[8] = {};
for (UINT i = 0; i < count; ++i)
{
d.names[i] = names[i];
barriers[i].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barriers[i].Transition.pResource = textures[i];
barriers[i].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barriers[i].Transition.StateBefore = state;
barriers[i].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
}
list->ResourceBarrier(count, barriers);
for (UINT i = 0; i < count; ++i)
{
D3D12_TEXTURE_COPY_LOCATION dst = {}, src = {};
dst.pResource = d.buffer;
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
dst.PlacedFootprint = d.footprint[i];
src.pResource = textures[i];
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
for (UINT i = 0; i < count; ++i)
std::swap(barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter);
list->ResourceBarrier(count, barriers);
d.frame = frame;
d.pending = true;
}
inline void snapshot_after_dispatch(ID3D12GraphicsCommandList* list)
{
const UINT index = ++t_dispatchIndex; // 1-based: matches the D4R_FSR_RR_DISPATCH table (row 0 = start stamp)
static const uint32_t frame = env_uint("D4R_FSR_RR_DP_SNAPSHOT_FRAME", 1);
const auto& points = snapshot_points();
if (t_frame != frame || std::find(points.begin(), points.end(), index) == points.end())
return;
D3D12_RESOURCE_BARRIER uav = {};
uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
state().resourceBarrier(list, 1, &uav);
Snapshot snap{index, {}};
// the dispatch's root CBV, read through the CPU mapping of whichever recorded buffer holds it
for (size_t i = 0; i < created().size() && t_cbv != 0; ++i)
{
const Created& c = created()[i];
if (c.desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER)
continue;
const D3D12_GPU_VIRTUAL_ADDRESS va = c.resource->GetGPUVirtualAddress();
if (t_cbv < va || t_cbv + 1376 > va + c.desc.Width)
continue;
void* mapped = nullptr;
D3D12_RANGE range = {static_cast<SIZE_T>(t_cbv - va), static_cast<SIZE_T>(t_cbv - va + 1376)};
if (SUCCEEDED(c.resource->Map(0, &range, &mapped)))
{
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
char name[512];
std::snprintf(name, sizeof(name), "%s\\snap_%03u_cbv.raw", dir.c_str(), index);
if (FILE* f = std::fopen(name, "wb"))
{
std::fwrite(static_cast<const uint8_t*>(mapped) + (t_cbv - va), 1, 1376, f);
std::fclose(f);
}
D3D12_RANGE none = {0, 0};
c.resource->Unmap(0, &none);
logf("D4R_FSR_RR_DP_SNAPSHOT: dispatch %u CBV in resource %zu at +%llu", index, i,
static_cast<unsigned long long>(t_cbv - va));
}
break;
}
// CPU-visible buffers (upload copies, e.g. the weights): read through their mapping
for (size_t i = 0; i < created().size(); ++i)
{
const Created& c = created()[i];
D3D12_HEAP_PROPERTIES props = {};
D3D12_HEAP_FLAGS flags = {};
if (c.desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER || FAILED(c.resource->GetHeapProperties(&props, &flags)) ||
props.Type != D3D12_HEAP_TYPE_UPLOAD)
continue;
void* mapped = nullptr;
if (SUCCEEDED(c.resource->Map(0, nullptr, &mapped)))
{
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
char name[512];
std::snprintf(name, sizeof(name), "%s\\snap_%03u_upload%02zu.raw", dir.c_str(), index, i);
if (FILE* f = std::fopen(name, "wb"))
{
std::fwrite(mapped, 1, c.desc.Width, f);
std::fclose(f);
}
D3D12_RANGE none = {0, 0};
c.resource->Unmap(0, &none);
}
}
if (t_cbv != 0)
logf("D4R_FSR_RR_DP_SNAPSHOT: dispatch %u root CBV va 0x%llx", index, static_cast<unsigned long long>(t_cbv));
for (size_t i = 0; i < created().size(); ++i)
{
const Created& c = created()[i];
if (c.desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER ||
(c.desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) == 0)
continue;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC buffer = c.desc;
buffer.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* readback = nullptr;
if (FAILED(t_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &buffer, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&readback))))
continue;
D3D12_RESOURCE_BARRIER t = {};
t.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
t.Transition.pResource = c.resource;
t.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
t.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
state().resourceBarrier(list, 1, &t);
list->CopyBufferRegion(readback, 0, c.resource, 0, c.desc.Width);
std::swap(t.Transition.StateBefore, t.Transition.StateAfter);
state().resourceBarrier(list, 1, &t);
snap.copies.push_back({i, readback});
}
snapshots().push_back(std::move(snap));
}
inline void snapshot_flush()
{
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
for (Snapshot& snap : snapshots())
{
for (auto& [index, readback] : snap.copies)
{
void* mapped = nullptr;
if (SUCCEEDED(readback->Map(0, nullptr, &mapped)))
{
char name[512];
std::snprintf(name, sizeof(name), "%s\\snap_%03u_res%02zu.raw", dir.c_str(), snap.dispatch, index);
if (FILE* f = std::fopen(name, "wb"))
{
std::fwrite(mapped, 1, created()[index].desc.Width, f);
std::fclose(f);
}
readback->Unmap(0, nullptr);
}
readback->Release();
}
logf("D4R_FSR_RR_DP_SNAPSHOT: dispatch %u written (%zu buffers)", snap.dispatch, snap.copies.size());
}
snapshots().clear();
}
inline void dump_flush()
{
snapshot_flush();
for (int set = 0; set < 2; ++set)
for (Dump& d : dumps(set))
dump_write(d);
}
} // namespace fsr_rr_dp
+24 -1
View File
@@ -37,6 +37,24 @@
#include "windows/fsr_rr_identity.h" #include "windows/fsr_rr_identity.h"
#include "fsr_rr_scene.h" #include "fsr_rr_scene.h"
// Per-dispatch GPU timing of AMD's denoiser (D4R_FSR_RR_DISPATCH_PROFILE=1), shared with the Linux shim.
inline void logf(const char* format, ...)
{
va_list args;
va_start(args, format);
std::vfprintf(stderr, format, args);
va_end(args);
std::fputc('\n', stderr);
}
inline std::string env_string(const char* name) { const char* v = std::getenv(name); return v ? v : ""; }
inline uint32_t env_uint(const char* name, uint32_t fallback)
{
const char* v = std::getenv(name);
return v && *v ? static_cast<uint32_t>(std::strtoul(v, nullptr, 0)) : fallback;
}
inline uint32_t profile_log_every() { return env_uint("D4R_PROFILE_LOG_EVERY", 1000000); }
#include "d4r_fsr_rr_dispatch_profile.h"
namespace namespace
{ {
using namespace fsr_scene; using namespace fsr_scene;
@@ -425,6 +443,7 @@ int wmain(int argc, wchar_t** argv)
// 3. Context (shader pipelines are created here: the slow and the most likely to fail part). // 3. Context (shader pipelines are created here: the slow and the most likely to fail part).
ffxContext context = nullptr; ffxContext context = nullptr;
const auto createStart = std::chrono::steady_clock::now(); const auto createStart = std::chrono::steady_clock::now();
fsr_rr_dp::hook_device(g_device);
const ffxReturnCode_t created = fsr_rr_create_context(runtime, g_device, width, height, signals, 0, context); const ffxReturnCode_t created = fsr_rr_create_context(runtime, g_device, width, height, signals, 0, context);
const double createMs = const double createMs =
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - createStart).count(); std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - createStart).count();
@@ -574,7 +593,9 @@ int wmain(int argc, wchar_t** argv)
} }
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0); list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0);
const auto cpuStart = std::chrono::steady_clock::now(); const auto cpuStart = std::chrono::steady_clock::now();
fsr_rr_dp::begin(g_device, list, f);
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header); const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
fsr_rr_dp::end(list);
cpuMs.push_back(std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpuStart).count()); 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->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 1);
list->ResolveQueryData(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0, 2, timestampBuffer, 0); list->ResolveQueryData(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0, 2, timestampBuffer, 0);
@@ -666,7 +687,9 @@ int wmain(int argc, wchar_t** argv)
} }
previousCamera = camera; previousCamera = camera;
} }
json_field("frame_log", frameLog + "\n ]"); if (fsr_rr_dp::enabled())
fsr_rr_dp::log_stats();
json_field("frame_log", frameLog +"\n ]");
if (dispatchOk) if (dispatchOk)
{ {
std::vector<double> warm(gpuMs.begin() + std::min<size_t>(gpuMs.size(), 3), gpuMs.end()); std::vector<double> warm(gpuMs.begin() + std::min<size_t>(gpuMs.size(), 3), gpuMs.end());
+7 -4
View File
@@ -25,6 +25,7 @@ def main():
for name in ('runner', 'model', 'case', 'output'): for name in ('runner', 'model', 'case', 'output'):
parser.add_argument('--' + name, required=True, type=lambda p: Path(p).resolve()) parser.add_argument('--' + name, required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--candidate', action='append', default=[], metavar='LABEL=OBJECT_DIRECTORY') parser.add_argument('--candidate', action='append', default=[], metavar='LABEL=OBJECT_DIRECTORY')
parser.add_argument('--candidate-runner', action='append', default=[], metavar='LABEL=EXECUTABLE')
parser.add_argument('--repeats', type=int, default=25) parser.add_argument('--repeats', type=int, default=25)
args = parser.parse_args() args = parser.parse_args()
if not args.output.is_relative_to((repo / 'build').resolve()): if not args.output.is_relative_to((repo / 'build').resolve()):
@@ -43,14 +44,16 @@ def main():
'runnerSha256': hashlib.sha256(args.runner.read_bytes()).hexdigest(), 'runnerSha256': hashlib.sha256(args.runner.read_bytes()).hexdigest(),
'caseSha256': hashlib.sha256(args.case.read_bytes()).hexdigest(), 'runs': []} 'caseSha256': hashlib.sha256(args.case.read_bytes()).hexdigest(), 'runs': []}
reference = None reference = None
variants = [('baseline', None)] + [tuple(s.split('=', 1)) for s in args.candidate] variants = [('baseline', None, args.runner)]
for label, source in variants: variants += [(label, source, args.runner) for label, source in (s.split('=', 1) for s in args.candidate)]
variants += [(label, None, Path(runner).resolve()) for label, runner in (s.split('=', 1) for s in args.candidate_runner)]
for label, source, runner in variants:
if not re.fullmatch(r'[a-zA-Z0-9_-]+', label): if not re.fullmatch(r'[a-zA-Z0-9_-]+', label):
parser.error('candidate label must be letters, digits, underscores or hyphens') parser.error('candidate label must be letters, digits, underscores or hyphens')
output = args.output / label output = args.output / label
output.mkdir() output.mkdir()
model = args.model model = args.model
row = {'label': label, 'objects': {}} row = {'label': label, 'objects': {}, 'runnerSha256': hashlib.sha256(runner.read_bytes()).hexdigest()}
if source: if source:
model = output / 'model' model = output / 'model'
shutil.copytree(args.model, model) shutil.copytree(args.model, model)
@@ -68,7 +71,7 @@ def main():
manifest['sha256'][name] = digest manifest['sha256'][name] = digest
manifest['experimentalOverlay'] = row['objects'] manifest['experimentalOverlay'] = row['objects']
(model / 'model.json').write_text(json.dumps(manifest, indent=2) + '\n') (model / 'model.json').write_text(json.dumps(manifest, indent=2) + '\n')
command = [str(args.runner), str(model), str(args.case), str(output / 'frames'), str(args.repeats), 'hip'] command = [str(runner), str(model), str(args.case), str(output / 'frames'), str(args.repeats), 'hip']
with (output / 'stdout.log').open('wb') as out, (output / 'stderr.log').open('wb') as err: 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=240) result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=240)
row['exitCode'] = result.returncode row['exitCode'] = result.returncode
+40 -15
View File
@@ -1,4 +1,4 @@
"""Compare K input batching using identical synthetic frames and alternating runs. """Compare K/M interop and native candidates on identical alternating synthetic runs.
Uses an existing private runtime; never installs into a game. CPU interop timing 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. includes synchronization and NGX execution, and is not a GPU network-only time.
@@ -19,8 +19,13 @@ def main():
for key in ('probe', 'shim', 'runtime', 'core', 'dlss', 'output'): for key in ('probe', 'shim', 'runtime', 'core', 'dlss', 'output'):
parser.add_argument('--' + key, type=lambda s: Path(s).resolve(), required=True) parser.add_argument('--' + key, type=lambda s: Path(s).resolve(), required=True)
parser.add_argument('--resolution', default='1920x1080') parser.add_argument('--resolution', default='1920x1080')
parser.add_argument('--model', choices=('K', 'M'), default='K')
parser.add_argument('--pixel-profile', choices=('baseline', 'packed', 'unorm', 'depth-stencil'), default='baseline')
parser.add_argument('--candidate-shim', type=lambda s: Path(s).resolve(), help='compare a rebuilt shim with the same native kernels')
parser.add_argument('--candidate-batch-inputs', action='store_true', help='also batch input copies in candidate shim/native runs')
parser.add_argument('--input-resolution', help='defaults to nominal Quality (two-thirds per axis)') 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('--frames', type=int, default=16)
parser.add_argument('--timeout', type=int, default=300, help='per-process limit, including cold shader compilation')
parser.add_argument('--profile-stages', action='store_true', parser.add_argument('--profile-stages', action='store_true',
help='diagnostic CPU stage logging; adds overhead to timings') help='diagnostic CPU stage logging; adds overhead to timings')
parser.add_argument('--candidate-native', type=lambda s: Path(s).resolve(), parser.add_argument('--candidate-native', type=lambda s: Path(s).resolve(),
@@ -41,8 +46,12 @@ def main():
if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.resolution): if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.resolution):
parser.error('resolution must be WIDTHxHEIGHT') parser.error('resolution must be WIDTHxHEIGHT')
ow, oh = map(int, args.resolution.split('x')) ow, oh = map(int, args.resolution.split('x'))
preset = 11 if args.model == 'K' else 13
comparing = bool(args.candidate_native or args.candidate_shim)
if args.candidate_batch_inputs and not comparing:
parser.error('--candidate-batch-inputs requires a candidate shim or native directory')
if not args.input_resolution: if not args.input_resolution:
args.input_resolution = f'{ow * 2 // 3}x{oh * 2 // 3}' args.input_resolution = f'{ow * 2 // 3}x{oh * 2 // 3}' if args.model == 'K' else f'{ow // 2}x{oh // 2}'
if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.input_resolution): if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.input_resolution):
parser.error('input-resolution must be WIDTHxHEIGHT') parser.error('input-resolution must be WIDTHxHEIGHT')
build_root = Path(__file__).resolve().parents[2] / 'build' build_root = Path(__file__).resolve().parents[2] / 'build'
@@ -53,7 +62,8 @@ def main():
'inputResolution': args.input_resolution, 'inputResolution': args.input_resolution,
'stageProfiling': args.profile_stages, 'stageProfiling': args.profile_stages,
'kernelProfiling': args.profile_kernels, 'kernelProfiling': args.profile_kernels,
'comparison': 'native kernels' if args.candidate_native else 'input batching', 'model': args.model, 'pixelProfile': args.pixel_profile,
'comparison': 'shim' if args.candidate_shim else 'native kernels' if args.candidate_native else 'input batching',
'timing': 'CPU interop including NGX and synchronization', 'runs': []} 'timing': 'CPU interop including NGX and synchronization', 'runs': []}
runtime = args.runtime runtime = args.runtime
native = runtime / 'native' native = runtime / 'native'
@@ -64,6 +74,7 @@ def main():
report['playerConfig'] = args.player_config report['playerConfig'] = args.player_config
report['sequence'] = args.sequence report['sequence'] = args.sequence
report['resetEveryFrame'] = args.reset_every_frame report['resetEveryFrame'] = args.reset_every_frame
report['candidateBatchInputs'] = args.candidate_batch_inputs
module = args.shim module = args.shim
if args.player_config: if args.player_config:
runtime = args.output / 'player' runtime = args.output / 'player'
@@ -79,6 +90,8 @@ def main():
nvapi = runtime / 'nvapi/gfx1101/nvapi64.dll' nvapi = runtime / 'nvapi/gfx1101/nvapi64.dll'
report['artifacts'] = {str(p): hashlib.sha256(p.read_bytes()).hexdigest() report['artifacts'] = {str(p): hashlib.sha256(p.read_bytes()).hexdigest()
for p in (args.probe, args.shim, args.core, args.dlss)} for p in (args.probe, args.shim, args.core, args.dlss)}
if args.candidate_shim:
report['candidateShimSha256'] = hashlib.sha256(args.candidate_shim.read_bytes()).hexdigest()
report['nativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() report['nativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted(native.glob('*')) if p.is_file()} for p in sorted(native.glob('*')) if p.is_file()}
if args.candidate_native: if args.candidate_native:
@@ -108,26 +121,34 @@ def main():
reference = None reference = None
variants = {'abba': (False, True, True, False), 'controls': (False,) * 4, 'batches': (True,) * 4} variants = {'abba': (False, True, True, False), 'controls': (False,) * 4, 'batches': (True,) * 4}
for index, batched in enumerate(variants[args.sequence]): for index, batched in enumerate(variants[args.sequence]):
label = ('native-candidate' if args.candidate_native else 'batch') if batched else 'control' use_batch = batched and (not comparing or args.candidate_batch_inputs)
label = ('candidate' if comparing else 'batch') if batched else 'control'
output = args.output / f'{index}-{label}' output = args.output / f'{index}-{label}'
output.mkdir() output.mkdir()
env['D4R_DIAG_DIR'] = str(output) env['D4R_DIAG_DIR'] = str(output)
if batched and not args.candidate_native: if use_batch:
env['D4R_BATCH_INPUT_COPIES'] = '1' env['D4R_BATCH_INPUT_COPIES'] = '1'
else: else:
env.pop('D4R_BATCH_INPUT_COPIES', None) env.pop('D4R_BATCH_INPUT_COPIES', None)
env['D4R_ZLUDA_NATIVE_DIR'] = str(args.candidate_native if batched and args.candidate_native else native) env['D4R_ZLUDA_NATIVE_DIR'] = str(args.candidate_native if batched and args.candidate_native else native)
if args.player_config: if args.player_config:
config = (Path(__file__).resolve().parents[2] / 'packaging/windows/d4r.ini').read_text() config = (Path(__file__).resolve().parents[2] / 'packaging/windows/d4r.ini').read_text()
config = re.sub(r'(?m)^Model\s*=\s*K\s*$', 'Model = ' + args.model, config)
config = re.sub(r'(?m)^BatchInputCopies\s*=\s*(?:true|false)\s*$', config = re.sub(r'(?m)^BatchInputCopies\s*=\s*(?:true|false)\s*$',
'BatchInputCopies = ' + str(batched and not args.candidate_native).lower(), config) 'BatchInputCopies = ' + str(use_batch).lower(), config)
selected_native = args.candidate_native if batched and args.candidate_native else native selected_native = args.candidate_native if batched and args.candidate_native else native
config = config.replace('NativeDir = auto', 'NativeDir = ' + str(selected_native)) config = config.replace('NativeDir = auto', 'NativeDir = ' + str(selected_native))
(runtime / 'd4r.ini').write_text(config) (runtime / 'd4r.ini').write_text(config)
command = [str(args.probe), '--module', str(module), '--hip-root', str(runtime / 'hip'), if args.candidate_shim:
shutil.copy2(args.candidate_shim if batched else args.shim, module)
selected_module = module if args.player_config else args.candidate_shim if batched and args.candidate_shim else module
command = [str(args.probe), '--module', str(selected_module), '--hip-root', str(runtime / 'hip'),
'--cuda-dll', str(runtime / 'zluda/nvcuda.dll'), '--ngx-core', str(args.core), '--cuda-dll', str(runtime / 'zluda/nvcuda.dll'), '--ngx-core', str(args.core),
'--dlss-dll', str(args.dlss), '--nvapi-dll', str(nvapi), '--dlss-dll', str(args.dlss), '--nvapi-dll', str(nvapi),
'--ngx-abi', 'project', '--interop-mode', 'command-list', '--preset', '11', '--ngx-abi', 'project', '--interop-mode', 'command-list', '--preset', str(preset),
'--ngx-quality', '2' if args.model == 'K' else '0',
'--ngx-create-flags', '0' if args.model == 'K' else '11',
'--pixel-profile', args.pixel_profile,
'--ngx-output-resolution', args.resolution, '--ngx-output-resolution', args.resolution,
'--ngx-input-resolution', args.input_resolution, '--iterations', str(args.frames)] '--ngx-input-resolution', args.input_resolution, '--iterations', str(args.frames)]
if args.player_config: if args.player_config:
@@ -135,7 +156,7 @@ def main():
if args.reset_every_frame: if args.reset_every_frame:
command += ['--reset-every-frame', '1'] command += ['--reset-every-frame', '1']
with (output / 'stdout.log').open('wb') as out, (output / 'stderr.log').open('wb') as err: 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) result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=args.timeout)
if args.player_config and (runtime / 'd4r_nvngx.log').exists(): if args.player_config and (runtime / 'd4r_nvngx.log').exists():
shutil.copy2(runtime / 'd4r_nvngx.log', output / 'player.log') shutil.copy2(runtime / 'd4r_nvngx.log', output / 'player.log')
text = (output / 'stdout.log').read_text(errors='replace') text = (output / 'stdout.log').read_text(errors='replace')
@@ -143,17 +164,21 @@ def main():
raise RuntimeError(f'run {index} failed ({result.returncode}); inspect {output}') raise RuntimeError(f'run {index} failed ({result.returncode}); inspect {output}')
if args.reset_every_frame and 'D3D12_RESET every_frame=1' not in text: if args.reset_every_frame and 'D3D12_RESET every_frame=1' not in text:
raise RuntimeError('Requested reset diagnostic was not applied') raise RuntimeError('Requested reset diagnostic was not applied')
if 'D3D12_PIXEL_PROFILE ' + args.pixel_profile not in text:
raise RuntimeError('Requested pixel formats were not applied')
iw, ih = map(int, args.input_resolution.split('x')) iw, ih = map(int, args.input_resolution.split('x'))
expected_size = f'D3D12_NGX_DIMENSIONS input={iw}x{ih} output={ow}x{oh}' 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: if expected_size not in text or f'D4R_INTEROP async=0 batch_inputs={int(use_batch)}' not in text:
raise RuntimeError(f'run {index}: requested dimensions/interop mode not applied') 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: if len(re.findall(rf'D3D12_OUTPUT preset={preset} frame=\d+ finite=1 ', text)) != args.frames:
raise RuntimeError(f'run {index}: missing finite-output checks') 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)] times = [float(x) for x in re.findall(r'D4R_FRAME .*?interop_ngx_ms=([0-9.]+)', text)]
if len(times) != args.frames: if len(times) != args.frames:
raise RuntimeError(f'run {index}: incomplete frame timing') raise RuntimeError(f'run {index}: incomplete frame timing')
hashes = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in output.glob('output-*.rgba16f')} hashes = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in output.glob('output-*')
if len(hashes) != args.frames: if p.suffix in ('.rgba16f', '.r11g11b10f', '.bgra8')}
expected_products = args.frames * (2 if args.pixel_profile in ('packed', 'unorm') else 1)
if len(hashes) != expected_products:
raise RuntimeError(f'run {index}: incomplete output readback') raise RuntimeError(f'run {index}: incomplete output readback')
if reference is None: if reference is None:
reference = hashes reference = hashes
@@ -162,7 +187,7 @@ def main():
report['differentFrames'] = [name for name, digest in hashes.items() if reference.get(name) != digest] report['differentFrames'] = [name for name, digest in hashes.items() if reference.get(name) != digest]
report['mismatchedOutputHashes'] = hashes report['mismatchedOutputHashes'] = hashes
raise RuntimeError(f'run {index}: output differs from control') raise RuntimeError(f'run {index}: output differs from control')
report['runs'].append({'batch': batched and not bool(args.candidate_native), report['runs'].append({'batch': use_batch,
'candidate': batched, 'allFrameMs': times, 'candidate': batched, 'allFrameMs': times,
'medianMs': statistics.median(times[4:]), 'outputHashes': hashes}) 'medianMs': statistics.median(times[4:]), 'outputHashes': hashes})
print(f'run={index} variant={label} warmed_cpu_interop_ms={statistics.median(times[4:]):.3f}', flush=True) print(f'run={index} variant={label} warmed_cpu_interop_ms={statistics.median(times[4:]):.3f}', flush=True)
@@ -170,7 +195,7 @@ def main():
values = [t for row in report['runs'] if row['candidate'] == mode for t in row['allFrameMs'][4:]] values = [t for row in report['runs'] if row['candidate'] == mode for t in row['allFrameMs'][4:]]
if not values: if not values:
continue continue
key = ('candidateMedianMs' if args.candidate_native else 'batchMedianMs') if mode else 'controlMedianMs' key = ('candidateMedianMs' if comparing else 'batchMedianMs') if mode else 'controlMedianMs'
report[key] = statistics.median(values) report[key] = statistics.median(values)
report['passed'] = True report['passed'] = True
except Exception as error: except Exception as error:
+107 -3
View File
@@ -16,6 +16,7 @@ import hashlib
import json import json
import os import os
import shutil import shutil
import struct
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
@@ -44,11 +45,98 @@ def git(args, cwd, log, identity=False):
run(prefix + list(args), cwd=cwd, log=log) run(prefix + list(args), cwd=cwd, log=log)
def generate_shader_overrides(source, output, shader_dump, optimizer, hashes, log, variants=()):
"""Embed local, measured optimizations only when the original translation matches exactly."""
if not hashes:
(source / 'libs/vkd3d-shader/d4r_shader_overrides.h').write_text(
'/* Generated by tools/windows/build_fsr_payload.py. No local overrides selected. */\n'
'static bool d4r_shader_replace_builtin(vkd3d_shader_hash_t hash, const void *original,\n'
' size_t original_size, const void **data, size_t *size)\n{\n'
' (void)hash; (void)original; (void)original_size; (void)data; (void)size;\n'
' return false;\n}\n')
return {}
if shader_dump is None or optimizer is None:
raise ValueError('--optimize-shader requires --shader-dump and --spirv-opt')
records = {}
arrays = []
entries = []
inputs = output / 'shader-sources'
inputs.mkdir()
for shader_hash, variant in ((h, v) for h in sorted(set(hashes)) for v in range(1 + len(variants))):
if len(shader_hash) != 16 or any(c not in '0123456789abcdef' for c in shader_hash):
raise ValueError('shader hash must contain 16 lowercase hexadecimal digits')
original = ([shader_dump] + list(variants))[variant] / (shader_hash + '.spv')
variant_inputs = inputs if variant == 0 else inputs / ('variant-' + str(variant))
variant_inputs.mkdir(exist_ok=True)
symbol = shader_hash if variant == 0 else shader_hash + '_' + str(variant)
optimized = variant_inputs / (shader_hash + '.optimized.spv')
run([optimizer, '--target-env=vulkan1.3', '-O', original, '-o', optimized], log=log)
validator = optimizer.with_name('spirv-val.exe')
run([validator, '--target-env', 'vulkan1.3', optimized], log=log)
shutil.copy2(original, variant_inputs / original.name)
for kind, path in (('original', original), ('optimized', optimized)):
data = path.read_bytes()
if len(data) % 4 or data[:4] != b'\x03\x02\x23\x07':
raise ValueError('invalid SPIR-V: ' + str(path))
words = struct.unpack('<%dI' % (len(data) // 4), data)
rows = [', '.join('0x%08xu' % word for word in words[i:i + 8]) for i in range(0, len(words), 8)]
arrays.append('static const uint32_t d4r_%s_%s[] = {\n%s\n};\n' %
(kind, symbol, ',\n'.join(rows)))
entries.append(' {0x%sULL, d4r_original_%s, sizeof(d4r_original_%s), '
'd4r_optimized_%s, sizeof(d4r_optimized_%s)},' % (shader_hash, symbol, symbol, symbol, symbol))
records[symbol] = {'originalSha256': sha(original), 'optimizedSha256': sha(optimized)}
header = '''/* Local shader optimizations. Generated by tools/windows/build_fsr_payload.py. */
''' + ''.join(arrays) + '''
static const struct d4r_shader_override {
vkd3d_shader_hash_t hash;
const void *original;
size_t original_size;
const void *optimized;
size_t optimized_size;
} d4r_shader_overrides[] = {
''' + '\n'.join(entries) + '''
};
static bool d4r_shader_replace_builtin(vkd3d_shader_hash_t hash, const void *original,
size_t original_size, const void **data, size_t *size)
{
char disabled[2];
unsigned int i;
if (vkd3d_get_env_var("D4R_FSR_DISABLE_BUILTIN_SHADERS", disabled, sizeof(disabled)))
return false;
for (i = 0; i < ARRAY_SIZE(d4r_shader_overrides); ++i)
{
const struct d4r_shader_override *entry = &d4r_shader_overrides[i];
void *buffer;
if (entry->hash != hash || entry->original_size != original_size ||
memcmp(entry->original, original, original_size))
continue;
if (!(buffer = vkd3d_malloc(entry->optimized_size)))
return false;
memcpy(buffer, entry->optimized, entry->optimized_size);
*data = buffer;
*size = entry->optimized_size;
INFO("d4r: using validated local optimization for shader %016"PRIx64".\\n", hash);
return true;
}
return false;
}
'''
(source / 'libs/vkd3d-shader/d4r_shader_overrides.h').write_text(header)
(inputs / 'd4r_shader_overrides.h').write_text(header)
records['optimizerSha256'] = sha(optimizer)
(inputs / 'manifest.json').write_text(json.dumps(records, indent=2) + '\n')
return records
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve()) parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--source', type=lambda p: Path(p).resolve(), parser.add_argument('--source', type=lambda p: Path(p).resolve(),
help='reuse an existing vkd3d-proton clone (must be a checkout of the pinned commit); default: fresh clone under --output/_work') help='reuse an existing vkd3d-proton clone (must be a checkout of the pinned commit); default: fresh clone under --output/_work')
parser.add_argument('--shader-dump', type=lambda p: Path(p).resolve(), help='original SPIR-V from this translator/device')
parser.add_argument('--shader-variant', action='append', default=[], type=lambda p: Path(p).resolve(), help='additional exact translation variant directory')
parser.add_argument('--spirv-opt', type=lambda p: Path(p).resolve(), help='local spirv-opt.exe used for measured optimizations')
parser.add_argument('--optimize-shader', action='append', default=[], metavar='HASH', help='embed a measured -O shader; repeat for each validated winner')
args = parser.parse_args() args = parser.parse_args()
if not args.output.is_relative_to((repo / 'build').resolve()): if not args.output.is_relative_to((repo / 'build').resolve()):
parser.error('output must stay under ignored build/') parser.error('output must stay under ignored build/')
@@ -85,12 +173,25 @@ def main():
if not ancestor: if not ancestor:
git(['cherry-pick', '-x'] + listing, dxil, log, identity=True) git(['cherry-pick', '-x'] + listing, dxil, log, identity=True)
# libc++ include fixes (GCC pulled these in transitively; libc++ does not) # libc++ include fixes (GCC pulled these in transitively; libc++ does not)
# and late shader replacement, preserving dxil-spirv's reflected metadata.
for patch, where in (('0001-dxil-spirv-include-exception-for-libcxx.patch', dxil), for patch, where in (('0001-dxil-spirv-include-exception-for-libcxx.patch', dxil),
('0002-dxbc-spirv-include-new-for-libcxx.patch', dxil / 'subprojects/dxbc-spirv')): ('0002-dxbc-spirv-include-new-for-libcxx.patch', dxil / 'subprojects/dxbc-spirv'),
('0003-preserve-meta-for-shader-overrides.patch', source),
('0004-support-local-optimized-shaders.patch', source)):
full = repo / 'patches/vkd3d-proton-windows' / patch full = repo / 'patches/vkd3d-proton-windows' / patch
check = subprocess.run(['git', 'apply', '--check', '--reverse', str(full)], cwd=where, capture_output=True) check = subprocess.run(['git', 'apply', '--check', '--reverse', str(full)], cwd=where, capture_output=True)
if check.returncode: # not applied yet if check.returncode: # not applied yet
git(['apply', full], where, log) git(['apply', full], where, log)
optimized_shaders = generate_shader_overrides(source, args.output, args.shader_dump, args.spirv_opt,
args.optimize_shader, log, args.shader_variant)
# Cache identities must include local patches and shader contents, not only upstream HEAD.
build_identity = hashlib.sha256()
build_identity.update((VKD3D_COMMIT + DXIL_FSR_MERGE).encode())
for patch in sorted((repo / 'patches/vkd3d-proton-windows').glob('*.patch')):
build_identity.update(patch.read_bytes())
build_identity.update(json.dumps(optimized_shaders, sort_keys=True).encode())
build_id = build_identity.hexdigest()[:16]
(source / 'vkd3d_build.h.in').write_text('#include <stdint.h>\nstatic const uint64_t vkd3d_build = 0x' + build_id + ';\n')
escaped = lambda path: str(path).replace('\\', '/') escaped = lambda path: str(path).replace('\\', '/')
cross = work / 'cross.txt' cross = work / 'cross.txt'
cross.write_text('\n'.join([ cross.write_text('\n'.join([
@@ -129,15 +230,18 @@ def main():
'fsr/d4r_vkd3d_core.dll is vkd3d-proton (LGPL-2.1) built for Windows by tools/windows/build_fsr_payload.py.', 'fsr/d4r_vkd3d_core.dll is vkd3d-proton (LGPL-2.1) built for Windows by tools/windows/build_fsr_payload.py.',
'Corresponding source: ' + VKD3D_URL + ' at ' + VKD3D_COMMIT + ',', 'Corresponding source: ' + VKD3D_URL + ' at ' + VKD3D_COMMIT + ',',
'dxil-spirv with upstream PR 307 (merge ' + DXIL_FSR_MERGE + ') cherry-picked, and the patches in', 'dxil-spirv with upstream PR 307 (merge ' + DXIL_FSR_MERGE + ') cherry-picked, and the patches in',
'patches/vkd3d-proton-windows/ of the d4r repository (libc++ include fixes). Build: meson cross build with', 'patches/vkd3d-proton-windows/ of the d4r repository (libc++ fixes and metadata-preserving shader overrides).',
'Build: meson cross build with',
'llvm-mingw clang, -Denable_extended_emulation=true, buildtype release; the DLL is stripped and renamed.', 'llvm-mingw clang, -Denable_extended_emulation=true, buildtype release; the DLL is stripped and renamed.',
'Built HEADs: ' + json.dumps(heads), 'Patches: ' + json.dumps(patches), 'Built HEADs: ' + json.dumps(heads), 'Patches: ' + json.dumps(patches),
'Cache build ID: ' + build_id, 'Local optimized shaders: ' + json.dumps(optimized_shaders),
'Local shader source inputs and generated header, when selected: shader-sources/.',
'fsr/amdxc64.dll is d4r original code (tools/windows/amdxc64_shim). It is not AMD software.', ''])) 'fsr/amdxc64.dll is d4r original code (tools/windows/amdxc64_shim). It is not AMD software.', '']))
files = {str(p.relative_to(args.output)).replace('\\', '/'): sha(p) for p in sorted(args.output.rglob('*')) files = {str(p.relative_to(args.output)).replace('\\', '/'): sha(p) for p in sorted(args.output.rglob('*'))
if p.is_file() and '_work' not in p.relative_to(args.output).parts} if p.is_file() and '_work' not in p.relative_to(args.output).parts}
(args.output / 'fsr-payload.json').write_text(json.dumps(dict( (args.output / 'fsr-payload.json').write_text(json.dumps(dict(
schema=1, kind='d4r-fsr-payload', amdDenoiserBundled=False, vkd3dProtonCommit=VKD3D_COMMIT, dxilSpirvFsrMerge=DXIL_FSR_MERGE, schema=1, kind='d4r-fsr-payload', amdDenoiserBundled=False, vkd3dProtonCommit=VKD3D_COMMIT, dxilSpirvFsrMerge=DXIL_FSR_MERGE,
builtHeads=heads, patches=patches, files=files), indent=2) + '\n') builtHeads=heads, patches=patches, optimizedShaders=optimized_shaders, cacheBuildId=build_id, files=files), indent=2) + '\n')
print(json.dumps(files, indent=2)) print(json.dumps(files, indent=2))
print('FSR_PAYLOAD_BUILT ' + str(args.output)) print('FSR_PAYLOAD_BUILT ' + str(args.output))
+14 -4
View File
@@ -34,10 +34,14 @@ def main():
parser.add_argument('--output-directory', type=pathlib.Path, required=True) parser.add_argument('--output-directory', type=pathlib.Path, required=True)
parser.add_argument('--gpu-arch', choices=tuple(TARGETS), default='gfx1201') parser.add_argument('--gpu-arch', choices=tuple(TARGETS), default='gfx1201')
parser.add_argument('--shader-mode', choices=['cu', 'wgp'], default='cu') parser.add_argument('--shader-mode', choices=['cu', 'wgp'], default='cu')
parser.add_argument('--wave64', action='store_true', help='experimental wave64 for M post kernels without matrix operations')
args = parser.parse_args() args = parser.parse_args()
if not re.fullmatch(r'hiluma_engine_(?:output_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr)(?:_max)?|' is_m_post = bool(re.fullmatch(r'rrlite_post_3_[12]_mv(?:hi|lo)_(?:hdr|ldr)_folded', args.kernel))
r'input_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr))_v2_rel', args.kernel): if not is_m_post and not re.fullmatch(r'hiluma_engine_(?:output_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr)(?:_max)?|'
parser.error('this private experimental build recipe supports K v2 input/output variants only') r'input_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr))_v2_rel', args.kernel):
parser.error('this private experimental build recipe supports K v2 input/output and M folded post variants only')
if args.wave64 and not is_m_post:
parser.error('--wave64 is restricted to M post kernels without matrix operations')
repo = pathlib.Path(__file__).resolve().parents[2] repo = pathlib.Path(__file__).resolve().parents[2]
output = args.output_directory.resolve() output = args.output_directory.resolve()
if not output.is_relative_to((repo / 'build').resolve()): if not output.is_relative_to((repo / 'build').resolve()):
@@ -70,6 +74,8 @@ def main():
for name in ('D4R_ZLUDA_IGNORE_DENORMAL', 'D4R_ZLUDA_FAST_MATH', 'D4R_ZLUDA_WMMA_F32ACC', for name in ('D4R_ZLUDA_IGNORE_DENORMAL', 'D4R_ZLUDA_FAST_MATH', 'D4R_ZLUDA_WMMA_F32ACC',
'D4R_ZLUDA_WAVE64', 'D4R_ZLUDA_PROFILE'): 'D4R_ZLUDA_WAVE64', 'D4R_ZLUDA_PROFILE'):
env.pop(name, None) env.pop(name, None)
if args.wave64:
env['D4R_ZLUDA_WAVE64'] = '1'
env['PATH'] = str(zluda) + os.pathsep + str(hip / 'bin') + os.pathsep + env['PATH'] env['PATH'] = str(zluda) + os.pathsep + str(hip / 'bin') + os.pathsep + env['PATH']
def run(label, command): def run(label, command):
@@ -94,7 +100,8 @@ def main():
raise RuntimeError('PTX recipe made no changes; refuse a no-op override') raise RuntimeError('PTX recipe made no changes; refuse a no-op override')
raw, ir, extra = work / 'raw.bc', work / 'raw.ll', work / 'extra.bc' raw, ir, extra = work / 'raw.bc', work / 'raw.ll', work / 'extra.bc'
run('compile-helper', [tools['clang++'], '-x', 'hip', '-std=c++20', '-nogpuinc', '-nogpulib', '-O3', run('compile-helper', [tools['clang++'], '-x', 'hip', '-std=c++20', '-nogpuinc', '-nogpulib', '-O3',
'-mno-wavefrontsize64', '--offload-device-only', '--offload-arch=' + args.gpu_arch, '-mwavefrontsize64' if args.wave64 else '-mno-wavefrontsize64',
'--offload-device-only', '--offload-arch=' + args.gpu_arch,
'-fgpu-rdc', '-emit-llvm', '-c', '-Xclang', '-fdenormal-fp-math=dynamic', '-fgpu-rdc', '-emit-llvm', '-c', '-Xclang', '-fdenormal-fp-math=dynamic',
'-DD4R_ACCURACY', '-DD4R_KERNEL_NAME=' + args.kernel, '-DD4R_ACCURACY', '-DD4R_KERNEL_NAME=' + args.kernel,
'-o', raw, repo / 'kernels/tex/sust_only.hip']) '-o', raw, repo / 'kernels/tex/sust_only.hip'])
@@ -122,10 +129,13 @@ def main():
expected_mode = '-cumode' if args.shader_mode == 'wgp' else '+cumode' expected_mode = '-cumode' if args.shader_mode == 'wgp' else '+cumode'
if not attributes or expected_mode not in attributes[1]: if not attributes or expected_mode not in attributes[1]:
raise RuntimeError(f'Emitter did not honor {args.shader_mode} mode; build the corresponding ZLUDA patch first') raise RuntimeError(f'Emitter did not honor {args.shader_mode} mode; build the corresponding ZLUDA patch first')
if args.wave64 and '+wavefrontsize64' not in attributes[1]:
raise RuntimeError('Emitter did not honor wave64; candidate is not accepted')
require_code_object_target(work / 'emitted/module.hsaco', args.gpu_arch) require_code_object_target(work / 'emitted/module.hsaco', args.gpu_arch)
shutil.copyfile(work / 'emitted/module.hsaco', output / (args.kernel + '.hsaco')) shutil.copyfile(work / 'emitted/module.hsaco', output / (args.kernel + '.hsaco'))
run('manifest', [sys.executable, repo / 'kernels/tools/kernel_manifest.py', output, args.dlss_dll]) run('manifest', [sys.executable, repo / 'kernels/tools/kernel_manifest.py', output, args.dlss_dll])
metadata = dict(architecture=args.gpu_arch, accuracy=True, nativeFP8=False, shaderMode=args.shader_mode, metadata = dict(architecture=args.gpu_arch, accuracy=True, nativeFP8=False, shaderMode=args.shader_mode,
wave64=args.wave64,
lastBuiltKernel=args.kernel, lastRecipeRewrites=rewrite_counts, lastBuiltKernel=args.kernel, lastRecipeRewrites=rewrite_counts,
privateNvidiaDerivedCode=True, validation='not yet validated; do not install before comparison', privateNvidiaDerivedCode=True, validation='not yet validated; do not install before comparison',
dlssSha256=hashlib.sha256(args.dlss_dll.read_bytes()).hexdigest(), dlssSha256=hashlib.sha256(args.dlss_dll.read_bytes()).hexdigest(),
+43 -9
View File
@@ -1,4 +1,4 @@
"""Build exact gfx11 RR D/E models natively on Windows, without Git Bash. """Build exact gfx11/gfx12 RR D/E models natively on Windows, without Git Bash.
Derived weights/shaders remain under ignored build/. Nothing is installed into 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. a game or bundled. Uses the same packers and compiler recipe as the Linux shell.
@@ -10,6 +10,7 @@ import os
from pathlib import Path from pathlib import Path
import re import re
import shutil import shutil
import struct
import subprocess import subprocess
import sys import sys
@@ -19,11 +20,33 @@ def sha(path):
return hashlib.file_digest(stream, 'sha256').hexdigest() return hashlib.file_digest(stream, 'sha256').hexdigest()
def require_fp8_matrices(path):
"""Reject an RDNA4 candidate that lost native FP8 cooperative matrices."""
data = path.read_bytes()
if len(data) < 20 or len(data) % 4:
raise RuntimeError('Invalid SPIR-V: ' + str(path))
words = struct.unpack('<' + 'I' * (len(data) // 4), data)
if words[0] != 0x07230203:
raise RuntimeError('Invalid SPIR-V magic: ' + str(path))
capabilities = set()
offset = 5
while offset < len(words):
size, opcode = words[offset] >> 16, words[offset] & 0xffff
if not size or offset + size > len(words):
raise RuntimeError('Invalid SPIR-V instruction: ' + str(path))
if opcode == 17 and size == 2: # OpCapability
capabilities.add(words[offset + 1])
offset += size
if not {4212, 4213, 6022}.issubset(capabilities):
raise RuntimeError('Native FP8 cooperative matrices are absent: ' + str(path))
def main(): def main():
repo = Path(__file__).resolve().parents[2] repo = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--dll', required=True, type=lambda p: Path(p).resolve()) parser.add_argument('--dll', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--preset', choices=('D', 'E'), default='D') parser.add_argument('--preset', choices=('D', 'E'), default='D')
parser.add_argument('--gpu-arch', choices=('gfx1101', 'gfx1200', 'gfx1201'), default='gfx1101')
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve()) 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('--glslang', type=lambda p: Path(p).resolve(), default=repo / '.tools/glslang-16.6.0/bin/glslangValidator.exe')
parser.add_argument('--reuse', action='store_true') parser.add_argument('--reuse', action='store_true')
@@ -38,7 +61,9 @@ def main():
for key in tuple(env): for key in tuple(env):
if key.startswith('D4R_RR_'): if key.startswith('D4R_RR_'):
del env[key] del env[key]
env.update(D4R_RR_GFX11='1', D4R_RR_RELAXED='0', PYTHONPATH=str(repo / '.tools/python/vendor')) gfx11 = args.gpu_arch.startswith('gfx11')
env.update(D4R_RR_GFX11='1' if gfx11 else '0', D4R_RR_RELAXED='0',
PYTHONPATH=str(repo / '.tools/python/vendor'))
env['PATH'] = str(args.glslang.parent) + os.pathsep + env['PATH'] 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() 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__))} sources = {str(Path(__file__).relative_to(repo)).replace('\\', '/'): sha(Path(__file__))}
@@ -46,7 +71,8 @@ def main():
for path in sorted((repo / directory).rglob('*')): for path in sorted((repo / directory).rglob('*')):
if path.is_file() and path.suffix in ('.py', '.comp', '.glsl', '.h', '.inc', '.sh', '.txt'): if path.is_file() and path.suffix in ('.py', '.comp', '.glsl', '.h', '.inc', '.sh', '.txt'):
sources[path.relative_to(repo).as_posix()] = sha(path) sources[path.relative_to(repo).as_posix()] = sha(path)
identity = {'schema': 1, 'preset': args.preset, 'architecture': 'gfx1101', 'arithmetic': 'exact-gfx11', identity = {'schema': 1, 'preset': args.preset, 'architecture': args.gpu_arch,
'arithmetic': 'exact-gfx11' if gfx11 else 'exact-gfx12',
'dll': sha(args.dll), 'glslang': sha(args.glslang), 'python': sha(Path(sys.executable)), 'dll': sha(args.dll), 'glslang': sha(args.glslang), 'python': sha(Path(sys.executable)),
'numpy': numpy_version, 'sources': sources} 'numpy': numpy_version, 'sources': sources}
stamp = args.output / 'windows-native-python-cache.json' stamp = args.output / 'windows-native-python-cache.json'
@@ -91,16 +117,24 @@ def main():
python(repo / 'engine/compile_rr.py', '--dll', args.dll, '--preset', args.preset, out, python(repo / 'engine/compile_rr.py', '--dll', args.dll, '--preset', args.preset, out,
'--ptx-dir', work / 'ptx', '--network-bindings-only', label='bindings') '--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') 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', manifest = (out / 'rr.txt').read_text()
f'preset {4 if args.preset == "D" else 5}\narithmetic gfx11\n') arithmetic = 'gfx11' if gfx11 else 'gfx12'
defines = ['-DRR_EXACT_CONVERT', '-DRR_GFX11'] + (['-DRR_PRESET_D'] if args.preset == 'D' else []) manifest = manifest.replace(f'preset {4 if args.preset == "D" else 5}\n',
f'preset {4 if args.preset == "D" else 5}\narithmetic {arithmetic}\n')
defines = ['-DRR_EXACT_CONVERT'] + (['-DRR_GFX11'] if gfx11 else [])
defines += ['-DRR_PRESET_D'] if args.preset == 'D' else []
def native(stage, shader, include): def native(stage, shader, include):
nonlocal manifest nonlocal manifest
weights = out / f'weights-{stage}m.bin' weights = out / f'weights-{stage}m.bin'
wide = int(python(rr / 'widen_weights.py', weights, label='widen-' + stage).splitlines()[-1]) widening = []
run([args.glslang, '--target-env', 'vulkan1.3', '-V', *defines, f'-DW_WIDE={wide}u', if gfx11:
wide = int(python(rr / 'widen_weights.py', weights, label='widen-' + stage).splitlines()[-1])
widening = [f'-DW_WIDE={wide}u']
run([args.glslang, '--target-env', 'vulkan1.3', '-V', *defines, *widening,
'-I' + str(include), '-I' + str(rr), '-o', out / f'{stage}_m.spv', shader], 'compile-' + stage) '-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) python(rr / 'spv_exact_convert.py', out / f'{stage}_m.spv', label='exact-' + stage)
if not gfx11:
require_fp8_matrices(out / f'{stage}_m.spv')
pattern = rf'^stage {stage} {stage}\.spv 32 weights-{stage}\.bin (.*)$' pattern = rf'^stage {stage} {stage}\.spv 32 weights-{stage}\.bin (.*)$'
def replacement(match): def replacement(match):
tail = match[1].removesuffix(' noopt') tail = match[1].removesuffix(' noopt')
@@ -139,7 +173,7 @@ def main():
path.unlink() path.unlink()
(out / 'rr.txt').write_text(manifest, newline='\n') (out / 'rr.txt').write_text(manifest, newline='\n')
stages = [line.split() for line in manifest.splitlines() if line.startswith('stage ')] stages = [line.split() for line in manifest.splitlines() if line.startswith('stage ')]
if len(stages) != 12 or 'arithmetic gfx11' not in manifest: if len(stages) != 12 or f'arithmetic {arithmetic}' not in manifest.splitlines():
raise RuntimeError('Incomplete native manifest') raise RuntimeError('Incomplete native manifest')
for fields in stages: for fields in stages:
if not (out / fields[2]).is_file() or not (out / fields[4]).is_file(): if not (out / fields[2]).is_file() or not (out / fields[4]).is_file():
+6 -2
View File
@@ -145,13 +145,17 @@ public:
api_.cuda.check(api_.cuMemcpy2DAsync_v2(&copy, nullptr), "cuMemcpy2DAsync_v2(external VRAM to array)"); api_.cuda.check(api_.cuMemcpy2DAsync_v2(&copy, nullptr), "cuMemcpy2DAsync_v2(external VRAM to array)");
} else api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(external VRAM to array)"); } else api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(external VRAM to array)");
} }
void download_device(CUdeviceptr destination, size_t pitch) { void download_device(CUdeviceptr destination, size_t pitch, bool deferred = false) {
if (pitch < rowBytes_) throw std::runtime_error("Device output pitch is smaller than its row"); if (pitch < rowBytes_) throw std::runtime_error("Device output pitch is smaller than its row");
Copy2D copy{}; Copy2D copy{};
copy.srcType = 3; copy.srcArray = array_; copy.srcType = 3; copy.srcArray = array_;
copy.dstType = 2; copy.dstDevice = destination; copy.dstPitch = pitch; copy.dstType = 2; copy.dstDevice = destination; copy.dstPitch = pitch;
copy.widthBytes = rowBytes_; copy.height = height_; copy.widthBytes = rowBytes_; copy.height = height_;
api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(array to external VRAM)"); if (deferred) {
// Match input batching: conversion follows on the shared legacy
// default stream, and the caller completes the batch before D3D12.
api_.cuda.check(api_.cuMemcpy2DAsync_v2(&copy, nullptr), "cuMemcpy2DAsync_v2(array to external VRAM)");
} else api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(array to external VRAM)");
} }
}; };
} }
+3 -1
View File
@@ -525,7 +525,9 @@ void STDMETHODCALLTYPE hook_execute(ID3D12CommandQueue* queue, UINT count, ID3D1
milliseconds(submitStart), boundaryMs, publishMs, drainMs); milliseconds(submitStart), boundaryMs, publishMs, drainMs);
} }
} catch (const std::exception& failure) { } catch (const std::exception& failure) {
{ std::lock_guard<std::mutex> lock(error_mutex); last_error = failure.what(); } // Only this submission fails. Command tracking is intact, so later frames
// record and run normally; a transient failure (one refused shared-buffer
// export after a feature recreate) must not disable DLSS for the session.
std::fprintf(stderr, "D4R_D3D12_SUBMIT_FAILURE %s\n", failure.what()); std::fprintf(stderr, "D4R_D3D12_SUBMIT_FAILURE %s\n", failure.what());
try { complete(queue); } catch (...) {} try { complete(queue); } catch (...) {}
// Do not submit a consumer suffix after a failed DLSS boundary. // Do not submit a consumer suffix after a failed DLSS boundary.
+7
View File
@@ -52,6 +52,13 @@ int main(int argc, char** argv) {
if (!found) throw std::runtime_error("HIP adapter missing from DXGI"); if (!found) throw std::runtime_error("HIP adapter missing from DXGI");
ComPtr<ID3D12Device> device; ComPtr<ID3D12Device> device;
dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "D3D12CreateDevice"); dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "D3D12CreateDevice");
// D4R_DIAG_VRAM_BALLAST_MB: occupy that much VRAM first, reproducing a game near its memory budget.
std::vector<ComPtr<ID3D12Resource>> ballast;
if (const char* mb = std::getenv("D4R_DIAG_VRAM_BALLAST_MB")) {
for (unsigned long long used = 0, total = std::stoull(mb); used + 256 <= total; used += 256)
ballast.push_back(make_buffer(device.Get(), 256ull << 20, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COMMON));
std::printf("D3D12_VRAM_BALLAST buffers=%zu mib=%zu\n", ballast.size(), ballast.size() * 256);
}
if (enhanced) { D3D12_FEATURE_DATA_D3D12_OPTIONS12 support{}; dx(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS12, &support, sizeof(support)), "Enhanced barrier support"); if (!support.EnhancedBarriersSupported) throw std::runtime_error("Enhanced barriers unavailable"); } if (enhanced) { D3D12_FEATURE_DATA_D3D12_OPTIONS12 support{}; dx(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS12, &support, sizeof(support)), "Enhanced barrier support"); if (!support.EnhancedBarriersSupported) throw std::runtime_error("Enhanced barriers unavailable"); }
auto enhancedBarrier = [&](ID3D12GraphicsCommandList* command, ID3D12Resource* texture, unsigned plane, bool initial) { auto enhancedBarrier = [&](ID3D12GraphicsCommandList* command, ID3D12Resource* texture, unsigned plane, bool initial) {
ComPtr<ID3D12GraphicsCommandList7> newer; dx(command->QueryInterface(IID_PPV_ARGS(newer.GetAddressOf())), "Harness enhanced list"); ComPtr<ID3D12GraphicsCommandList7> newer; dx(command->QueryInterface(IID_PPV_ARGS(newer.GetAddressOf())), "Harness enhanced list");
+111 -13
View File
@@ -4,6 +4,8 @@
#endif #endif
#include "hip_api.h" #include "hip_api.h"
#include <d3d12.h> #include <d3d12.h>
#include <dxgi1_4.h>
#include <mutex>
#include <wrl/client.h> #include <wrl/client.h>
namespace d4r::win { namespace d4r::win {
@@ -103,10 +105,106 @@ inline void texture_copy_barrier(ID3D12GraphicsCommandList* list, ID3D12Resource
class SharedPlane { class SharedPlane {
ExternalApi& api_; ExternalApi& api_;
hipExternalMemory_t memory_ = nullptr; hipExternalMemory_t memory_ = nullptr;
ComPtr<ID3D12Heap> heap_; // only for the explicit-heap export; outlives buffer (declared first)
void release() noexcept { void release() noexcept {
if (mapped) { (void)api_.hip.hipFree(mapped); mapped = nullptr; } if (mapped) { (void)api_.hip.hipFree(mapped); mapped = nullptr; }
if (memory_) { (void)api_.hipDestroyExternalMemory(memory_); memory_ = nullptr; } if (memory_) { (void)api_.hipDestroyExternalMemory(memory_); memory_ = nullptr; }
} }
// Export a committed shared buffer (the tested path). Returns false when the driver refuses
// the export: Cyberpunk 2077 saw CreateSharedHandle E_INVALIDARG on a fresh shared buffer
// after a DLSS feature recreate, although the same sequence works outside the game.
bool export_committed(ID3D12Device* device, Handle& handle, UINT64& importBytes) {
buffer = make_buffer(device, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(buffer.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
device->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = device->GetResourceAllocationInfo(0, 1, &desc);
#endif
const HRESULT result = device->CreateSharedHandle(buffer.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value);
if (SUCCEEDED(result)) { importBytes = allocation.SizeInBytes; return true; }
// A wrapping layer can hand back a resource whose owner is not the device it was created on.
ComPtr<ID3D12Device> owner; (void)buffer->GetDevice(IID_PPV_ARGS(owner.GetAddressOf()));
std::fprintf(stderr, "D4R_SHARED_PLANE_EXPORT committed HRESULT=0x%08lx bytes=%llu allocation=%llu device_removed=0x%08lx owner_matches=%d\n",
static_cast<unsigned long>(result), static_cast<unsigned long long>(bytes),
static_cast<unsigned long long>(allocation.SizeInBytes), static_cast<unsigned long>(device->GetDeviceRemovedReason()),
owner.Get() == device);
buffer.Reset(); handle.value = nullptr;
return false;
}
// Last resort when the device refuses every export (Cyberpunk 2077 with the FSR RR backend refused
// both the committed and the heap export): allocate and export on a private device of the same
// adapter, then open that allocation on the caller's device for the copies. HIP imports the
// private device's handle, which names the same memory.
ComPtr<ID3D12Device> privateDevice_;
ComPtr<ID3D12Resource> privateBuffer_;
static ComPtr<ID3D12Device> private_device(ID3D12Device* device) {
static std::mutex lock; static ComPtr<ID3D12Device> cached; static LUID cachedLuid{};
std::lock_guard<std::mutex> guard(lock);
const LUID luid = adapter_luid(device);
if (cached && cachedLuid.LowPart == luid.LowPart && cachedLuid.HighPart == luid.HighPart) return cached;
using CreateFactory = HRESULT(WINAPI*)(REFIID, void**);
HMODULE dxgi = LoadLibraryW(L"dxgi.dll");
auto create = dxgi ? reinterpret_cast<CreateFactory>(reinterpret_cast<void*>(GetProcAddress(dxgi, "CreateDXGIFactory1"))) : nullptr;
if (!create) throw std::runtime_error("CreateDXGIFactory1 unavailable (private shared-plane device)");
ComPtr<IDXGIFactory4> factory; dx(create(IID_PPV_ARGS(factory.GetAddressOf())), "CreateDXGIFactory1(private shared-plane device)");
ComPtr<IDXGIAdapter1> adapter; dx(factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(adapter.GetAddressOf())), "EnumAdapterByLuid(private shared-plane device)");
// D3D12CreateDevice returns the adapter's existing singleton (the game's own device), so an
// independent device comes from the device factory, neither reusing nor replacing the singleton.
using GetInterface = HRESULT(WINAPI*)(REFCLSID, REFIID, void**);
HMODULE d3d12 = GetModuleHandleW(L"d3d12.dll");
auto getInterface = d3d12 ? reinterpret_cast<GetInterface>(reinterpret_cast<void*>(GetProcAddress(d3d12, "D3D12GetInterface"))) : nullptr;
if (!getInterface) throw std::runtime_error("D3D12GetInterface unavailable (private shared-plane device)");
static const GUID deviceFactoryClsid = {0x114863bf, 0xc386, 0x4aee, {0xb3, 0x9d, 0x8f, 0x0b, 0xbb, 0x06, 0x29, 0x55}};
ComPtr<ID3D12DeviceFactory> deviceFactory;
dx(getInterface(deviceFactoryClsid, IID_PPV_ARGS(deviceFactory.GetAddressOf())), "D3D12GetInterface(device factory)");
dx(deviceFactory->SetFlags(D3D12_DEVICE_FACTORY_FLAG_DISALLOW_STORING_NEW_DEVICE_AS_SINGLETON), "Device factory flags");
ComPtr<ID3D12Device> created;
dx(deviceFactory->CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(created.GetAddressOf())), "Create private shared-plane device");
ComPtr<IUnknown> createdIdentity, callerIdentity;
(void)created->QueryInterface(IID_PPV_ARGS(createdIdentity.GetAddressOf()));
(void)device->QueryInterface(IID_PPV_ARGS(callerIdentity.GetAddressOf()));
std::fprintf(stderr, "D4R_SHARED_PLANE_PRIVATE_DEVICE independent=%d\n", createdIdentity.Get() != callerIdentity.Get());
cached = created; cachedLuid = luid;
return cached;
}
void export_private(ID3D12Device* device, Handle& handle, UINT64& importBytes) {
privateDevice_ = private_device(device);
privateBuffer_ = make_buffer(privateDevice_.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(privateBuffer_.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
privateDevice_->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = privateDevice_->GetResourceAllocationInfo(0, 1, &desc);
#endif
dx(privateDevice_->CreateSharedHandle(privateBuffer_.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value), "CreateSharedHandle(private device)");
dx(device->OpenSharedHandle(handle.value, IID_PPV_ARGS(buffer.GetAddressOf())), "OpenSharedHandle(private shared plane)");
importBytes = allocation.SizeInBytes;
}
// Fallback: share an explicit heap that holds the buffer as a placed resource.
bool export_heap(ID3D12Device* device, Handle& handle, UINT64& importBytes) {
constexpr UINT64 alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
D3D12_HEAP_DESC heap{};
heap.SizeInBytes = (bytes + alignment - 1) / alignment * alignment;
heap.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
heap.Properties.CreationNodeMask = heap.Properties.VisibleNodeMask = 1;
heap.Alignment = alignment;
heap.Flags = D3D12_HEAP_FLAG_SHARED | D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
dx(device->CreateHeap(&heap, IID_PPV_ARGS(heap_.GetAddressOf())), "CreateHeap(shared plane)");
D3D12_RESOURCE_DESC desc{};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Width = bytes;
desc.Height = desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
dx(device->CreatePlacedResource(heap_.Get(), 0, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr,
IID_PPV_ARGS(buffer.GetAddressOf())), "CreatePlacedResource(shared plane)");
const HRESULT result = device->CreateSharedHandle(heap_.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value);
if (SUCCEEDED(result)) { importBytes = heap.SizeInBytes; return true; }
std::fprintf(stderr, "D4R_SHARED_PLANE_EXPORT heap HRESULT=0x%08lx\n", static_cast<unsigned long>(result));
buffer.Reset(); heap_.Reset(); handle.value = nullptr;
return false;
}
public: public:
ComPtr<ID3D12Resource> buffer; ComPtr<ID3D12Resource> buffer;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
@@ -124,28 +222,28 @@ public:
} }
if (!bytes || bytes == UINT64_MAX || !footprint.Footprint.RowPitch) if (!bytes || bytes == UINT64_MAX || !footprint.Footprint.RowPitch)
throw std::runtime_error("Invalid shared texture footprint"); throw std::runtime_error("Invalid shared texture footprint");
buffer = make_buffer(device, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(buffer.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
device->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = device->GetResourceAllocationInfo(0, 1, &desc);
#endif
Handle handle; Handle handle;
dx(device->CreateSharedHandle(buffer.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value), "CreateSharedHandle(VRAM)"); UINT64 importBytes = 0;
// D4R_SHARED_PLANE_EXPORT=heap or =private forces that fallback, for testing it.
const char* forcedValue = std::getenv("D4R_SHARED_PLANE_EXPORT");
const std::string forced = forcedValue ? forcedValue : "";
const char* path = "committed";
bool exported = forced.empty() && (export_committed(device, handle, importBytes) || export_committed(device, handle, importBytes));
if (!exported && forced != "private") { path = "heap"; exported = export_heap(device, handle, importBytes); }
if (!exported) { path = "private"; export_private(device, handle, importBytes); }
const bool heapImport = std::string(path) == "heap";
try { try {
hipExternalMemoryHandleDesc import{}; hipExternalMemoryHandleDesc import{};
import.type = hipExternalMemoryHandleTypeD3D12Resource; import.type = heapImport ? hipExternalMemoryHandleTypeD3D12Heap : hipExternalMemoryHandleTypeD3D12Resource;
import.handle.win32.handle = handle.value; import.handle.win32.handle = handle.value;
import.size = allocation.SizeInBytes; import.flags = hipExternalMemoryDedicated; import.size = importBytes; import.flags = heapImport ? 0 : hipExternalMemoryDedicated;
api.hip.check(api.hipImportExternalMemory(&memory_, &import), "hipImportExternalMemory(D3D12)"); api.hip.check(api.hipImportExternalMemory(&memory_, &import), "hipImportExternalMemory(D3D12)");
hipExternalMemoryBufferDesc map{}; map.size = bytes; hipExternalMemoryBufferDesc map{}; map.size = bytes;
api.hip.check(api.hipExternalMemoryGetMappedBuffer(&mapped, memory_, &map), "hipExternalMemoryGetMappedBuffer"); api.hip.check(api.hipExternalMemoryGetMappedBuffer(&mapped, memory_, &map), "hipExternalMemoryGetMappedBuffer");
} catch (...) { release(); throw; } } catch (...) { release(); throw; }
// Win32 imports retain their own reference; the exporter closes its HANDLE. // Win32 imports retain their own reference; the exporter closes its HANDLE.
std::printf("D4R_IMPORT width=%u height=%u pitch=%u allocation=%llu cpu_copy=0\n", std::printf("D4R_IMPORT width=%u height=%u pitch=%u allocation=%llu export=%s cpu_copy=0\n",
footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.RowPitch, allocation.SizeInBytes); footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.RowPitch, importBytes, path);
} }
SharedPlane(const SharedPlane&) = delete; SharedPlane(const SharedPlane&) = delete;
~SharedPlane() { release(); } ~SharedPlane() { release(); }
+6 -1
View File
@@ -32,6 +32,9 @@ const Case cases[] = {
{DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, 4, "exposure-rgba32f"}, {DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, 4, "exposure-rgba32f"},
{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 4, "exposure-rgba8"}, {DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 4, "exposure-rgba8"},
{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, 4, "exposure-bgra8"}, {DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, 4, "exposure-bgra8"},
{DXGI_FORMAT_R9G9B9E5_SHAREDEXP, DXGI_FORMAT_R9G9B9E5_SHAREDEXP, 0, "rgb9e5"},
{DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_R16_FLOAT, 1, "depth-r16f"},
{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 2, "motion-rgba8"},
}; };
struct List { struct List {
ComPtr<ID3D12CommandAllocator> alloc; ComPtr<ID3D12CommandAllocator> alloc;
@@ -93,6 +96,7 @@ void fixture(uint8_t* row, unsigned y, PixelSpec spec) {
case d4r::pixel::rg16snorm: case d4r::pixel::rg16unorm: case d4r::pixel::r16unorm: case d4r::pixel::rg16snorm: case d4r::pixel::rg16unorm: case d4r::pixel::r16unorm:
for (unsigned c = 0; c < spec.bytes / 2; ++c) reinterpret_cast<uint16_t*>(p)[c] = uint16_t(i < 4 ? i * 32767 : i * 193 + c * 37); break; for (unsigned c = 0; c < spec.bytes / 2; ++c) reinterpret_cast<uint16_t*>(p)[c] = uint16_t(i < 4 ? i * 32767 : i * 193 + c * 37); break;
case d4r::pixel::depth24: *reinterpret_cast<uint32_t*>(p) = (i * 3791) | ((i % 256) << 24); break; case d4r::pixel::depth24: *reinterpret_cast<uint32_t*>(p) = (i * 3791) | ((i % 256) << 24); break;
case d4r::pixel::rgb9e5: *reinterpret_cast<uint32_t*>(p) = i * 2654435761u; break; // every exponent and mantissa
default: throw std::runtime_error("Missing diagnostic fixture"); default: throw std::runtime_error("Missing diagnostic fixture");
} }
} }
@@ -231,7 +235,8 @@ int main(int argc, char** argv) {
if (mismatch && differs++ < 5) std::fprintf(stderr, "PIXEL_DIFFERENT format=%s pixel=%u channel=%u expected=%08x actual=%08x\n", item.name, y * width + x, c, expected[i], got); if (mismatch && differs++ < 5) std::fprintf(stderr, "PIXEL_DIFFERENT format=%s pixel=%u channel=%u expected=%08x actual=%08x\n", item.name, y * width + x, c, expected[i], got);
} }
if (differs) throw std::runtime_error(std::string("D3D12 typed decode mismatch: ") + item.name + " count=" + std::to_string(differs)); if (differs) throw std::runtime_error(std::string("D3D12 typed decode mismatch: ") + item.name + " count=" + std::to_string(differs));
if (item.plane == 0) { // D3D12 has no typed UAV store for R9G9B9E5, so its encoder has no hardware reference here.
if (item.plane == 0 && spec.storage != d4r::pixel::rgb9e5) {
// BGRA uses the identical UNORM converter through an RGBA UAV; // BGRA uses the identical UNORM converter through an RGBA UAV;
// the consumer SRV independently tests the channel permutation. // the consumer SRV independently tests the channel permutation.
DXGI_FORMAT encodedFormat = spec.storage == d4r::pixel::bgra8 ? DXGI_FORMAT_R8G8B8A8_UNORM : item.view; DXGI_FORMAT encodedFormat = spec.storage == d4r::pixel::bgra8 ? DXGI_FORMAT_R8G8B8A8_UNORM : item.view;
+56 -21
View File
@@ -27,17 +27,26 @@ void initialize(ID3D12Device* device, unsigned long long app, const wchar_t* dat
const d4r::ngx::FeatureCommonInfo* common, const d4r::ngx::ProjectIdentity* project = nullptr) { const d4r::ngx::FeatureCommonInfo* common, const d4r::ngx::ProjectIdentity* project = nullptr) {
if (initialization) { if (initialization) {
if (initialization->device.Get() != device) throw std::runtime_error("NGX already initialized on another device"); if (initialization->device.Get() != device) throw std::runtime_error("NGX already initialized on another device");
return; // A deferred initialization whose SR runtime could not start yields to a later Init.
if (runtime || !deferredSr) return;
} }
auto candidate = std::make_shared<d4r::win::RuntimeInitialization>(device, app, data, sdk, common, project); auto candidate = std::make_shared<d4r::win::RuntimeInitialization>(device, app, data, sdk, common, project);
bool native = false; bool native = false;
#if defined(D4R_NATIVE_RR_BACKEND) #if defined(D4R_NATIVE_RR_BACKEND)
const char* backend = std::getenv("D4R_RR_BACKEND"); native = backend && (std::string(backend) == "native" || std::string(backend) == "fsr"); const char* backend = std::getenv("D4R_RR_BACKEND"); native = backend && (std::string(backend) == "native" || std::string(backend) == "fsr");
#endif #endif
// Default SR startup remains eager. Native RR owns its Vulkan execution // SR starts eagerly; RuntimeInitialization::create falls back to d4r's project identity when
// independently; SR initializes only when its capabilities/feature are used. // NGX rejects the game's application ID, so Streamline's first capability query sees SR and RR.
if (!native) runtime = candidate->create(); // Native RR owns its Vulkan execution independently, so with an RR backend a failed SR start
initialization = std::move(candidate); deferredSr = native; // (no SR files installed) keeps the initialization for RR alone, and a later Init replaces it.
try { runtime = candidate->create(); }
catch (const std::exception& error) {
if (!native) throw;
std::fprintf(stderr, "D4R_SR_UNAVAILABLE %s\n", error.what());
initialization = std::move(candidate); deferredSr = true;
return;
}
initialization = std::move(candidate); deferredSr = false;
} }
struct Handle { unsigned Id; }; struct Handle { unsigned Id; };
unsigned nextId = 1; unsigned nextId = 1;
@@ -161,26 +170,25 @@ bool fsr_ready() {
#if defined(D4R_FSR_RR_BACKEND) #if defined(D4R_FSR_RR_BACKEND)
const auto config = d4r::win::rr::FsrConfig::from_environment(); const auto config = d4r::win::rr::FsrConfig::from_environment();
auto present = [](const std::wstring& path) { return !path.empty() && GetFileAttributesW(path.c_str()) != INVALID_FILE_ATTRIBUTES; }; auto present = [](const std::wstring& path) { return !path.empty() && GetFileAttributesW(path.c_str()) != INVALID_FILE_ATTRIBUTES; };
const bool ready = present(config.vkd3dCore) && present(config.denoiserDll) && present(config.amdxcShim); const bool native = d4r::win::rr::FsrConfig::use_native_device();
if (!ready) std::fprintf(stderr, "D4R_RR_UNAVAILABLE FSR RR needs D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM to exist\n"); const bool ready = present(config.denoiserDll) && (native || (present(config.vkd3dCore) && present(config.amdxcShim)));
if (!ready) std::fprintf(stderr, "D4R_RR_UNAVAILABLE FSR RR needs %s\n", native ? "D4R_FSR_RR_DLL to exist" :
"D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM to exist");
return ready; return ready;
#else #else
return false; return false;
#endif #endif
} }
bool rr_ready(ID3D12Device* device) { // Readiness is a file check, like fsr_ready. Building a Backend here created a Vulkan device inside
// the game during Streamline's startup capability queries (about 30 s in Cyberpunk 2077), and
// OptiScaler's Streamline hooks then crashed when Streamline reloaded its plugins. The model, adapter
// and interop are validated when the game creates the RR feature; a failure there fails that call.
bool rr_ready() {
if (rr_is_fsr()) return fsr_ready(); if (rr_is_fsr()) return fsr_ready();
try { try {
const auto directory = rr_directory(); const auto directory = std::filesystem::path(d4r::diag::wide(rr_directory()));
void* p = d4r_ngx_parameters_create(); if (!std::filesystem::is_regular_file(directory / L"d4r-private-rr.json"))
if (!p) return false; throw std::runtime_error("RR model manifest missing: " + (directory / L"d4r-private-rr.json").string());
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; return true;
} catch (const std::exception& error) { } catch (const std::exception& error) {
if (std::getenv("D4R_RR_BACKEND")) std::fprintf(stderr, "D4R_RR_UNAVAILABLE %s\n", error.what()); if (std::getenv("D4R_RR_BACKEND")) std::fprintf(stderr, "D4R_RR_UNAVAILABLE %s\n", error.what());
@@ -193,6 +201,7 @@ struct RrSnapshot {
std::array<bool, d4r::win::rr::Snapshot::Count> explicitState{}; std::array<bool, d4r::win::rr::Snapshot::Count> explicitState{};
RrSnapshot(const d4r::win::rr::Creation& creation, void* p, ID3D12GraphicsCommandList* list) RrSnapshot(const d4r::win::rr::Creation& creation, void* p, ID3D12GraphicsCommandList* list)
: frame(creation, p) { : frame(creation, p) {
if (rr_is_fsr()) use_fsr_hit_distances(p);
const char* names[] = {"D4R.Color.State", "D4R.Motion.State", "D4R.Depth.State", "D4R.Normals.State", 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.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"}; "D4R.HitDistance.State", "D4R.Disocclusion.State", "D4R.Subsurface.State", "D4R.BeforeParticles.State"};
@@ -213,6 +222,33 @@ struct RrSnapshot {
access[4] = access[3]; explicitState[4] = explicitState[3]; access[4] = access[3]; explicitState[4] = explicitState[3];
} }
} }
// The Linux FSR backend's guide reading: a specular or diffuse hit distance may come as the w of a
// ray-direction texture, and either one makes that lobe an indirect signal, which AMD's denoiser filters
// far more strongly than a direct one. FSR has no disocclusion or subsurface input, so the diffuse hit
// distance travels in the Subsurface slot. Done before the access states below are captured.
void use_fsr_hit_distances(void* p) {
using d4r::win::rr::Snapshot;
auto origin = [&](const char* prefix) {
return std::array<unsigned, 2>{d4r::win::rr::number(p, (std::string(prefix) + ".Subrect.Base.X").c_str()),
d4r::win::rr::number(p, (std::string(prefix) + ".Subrect.Base.Y").c_str())};
};
if (!frame.resources[Snapshot::HitDistance]) {
if (auto* ray = Snapshot::resource(p, "DLSSD.SpecularRayDirectionHitDistance")) {
frame.resources[Snapshot::HitDistance] = ray; frame.specularHitInW = true;
frame.origins[Snapshot::HitDistance] = origin("DLSSD.SpecularRayDirectionHitDistance");
}
}
frame.resources[Snapshot::Disocclusion].Reset(); frame.resources[Snapshot::Subsurface].Reset();
const char* diffuseName = "DLSSD.DiffuseHitDistance";
ID3D12Resource* diffuse = Snapshot::resource(p, diffuseName);
if (!diffuse && (diffuse = Snapshot::resource(p, "DLSSD.DiffuseRayDirectionHitDistance"))) {
diffuseName = "DLSSD.DiffuseRayDirectionHitDistance"; frame.diffuseHitInW = true;
}
if (diffuse) {
frame.resources[Snapshot::Subsurface] = diffuse; frame.diffuseHit = true;
frame.origins[Snapshot::Subsurface] = origin(diffuseName);
}
}
void resolve() { void resolve() {
for (size_t i = 0; i < access.size(); ++i) if (access[i].inherited) { 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]); access[i] = d4r::win::commands::submitted_resource_access(frame.resources[i].Get(), access[i]);
@@ -273,7 +309,7 @@ unsigned allocate(void** out, bool caps) {
} }
bool rrAvailable = false; bool rrAvailable = false;
#if defined(D4R_NATIVE_RR_BACKEND) #if defined(D4R_NATIVE_RR_BACKEND)
rrAvailable = rr_ready(initialization->device.Get()); rrAvailable = rr_ready();
#endif #endif
d4r_ngx_set_int(value, "SuperSamplingDenoising.Available", rrAvailable ? 1 : 0); d4r_ngx_set_int(value, "SuperSamplingDenoising.Available", rrAvailable ? 1 : 0);
d4r_ngx_set_int(value, "SuperSamplingDenoising.NeedsUpdatedDriver", 0); d4r_ngx_set_int(value, "SuperSamplingDenoising.NeedsUpdatedDriver", 0);
@@ -320,8 +356,7 @@ API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const
#if defined(D4R_NATIVE_RR_BACKEND) #if defined(D4R_NATIVE_RR_BACKEND)
if (info->FeatureID == 13) { if (info->FeatureID == 13) {
d4r::win::player::ensure_environment(); d4r::win::player::ensure_environment();
d4r::win::ComPtr<ID3D12Device> device; if (rr_ready()) {
if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device))) && rr_ready(device.Get())) {
requirements->FeatureSupported = 0; requirements->FeatureSupported = 0;
std::strcpy(requirements->MinOSVersion, "10.0.22000.0"); std::strcpy(requirements->MinOSVersion, "10.0.22000.0");
} }
+18 -3
View File
@@ -61,6 +61,21 @@ def select_native(data, model):
return (b'\xef\xbb\xbf' if bom else b'') + ''.join(result).encode('utf-8') return (b'\xef\xbb\xbf' if bom else b'') + ''.join(result).encode('utf-8')
def model_arithmetic(identity, runtime):
"""Check the model's arithmetic and target before copying any products."""
architecture = identity.get('architecture')
arithmetic = {'gfx1101': 'gfx11', 'gfx1200': 'gfx12', 'gfx1201': 'gfx12'}.get(architecture)
if identity.get('preset') not in ('D', 'E') or not arithmetic or identity.get('arithmetic') != 'exact-' + arithmetic:
raise RuntimeError('Unsupported local RR cache identity')
target_file = runtime / 'gpu-target.json'
if target_file.is_file():
if json.loads(target_file.read_text(encoding='utf-8-sig')).get('architecture') != architecture:
raise RuntimeError('RR model architecture does not match runtime target')
elif arithmetic == 'gfx12':
raise RuntimeError('RDNA4 RR deployment requires runtime gpu-target.json')
return arithmetic
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--model', required=True, type=lambda p: Path(p).resolve()) parser.add_argument('--model', required=True, type=lambda p: Path(p).resolve())
@@ -77,8 +92,7 @@ def main():
cache_data = (args.model / 'windows-native-python-cache.json').read_bytes() cache_data = (args.model / 'windows-native-python-cache.json').read_bytes()
cache = json.loads(cache_data) cache = json.loads(cache_data)
identity = cache['identity'] identity = cache['identity']
if identity.get('preset') not in ('D', 'E') or identity.get('architecture') != 'gfx1101' or identity.get('arithmetic') != 'exact-gfx11': arithmetic = model_arithmetic(identity, args.runtime)
raise RuntimeError('Unsupported local RR cache identity')
products = cache['products'] products = cache['products']
if not {'rr.txt', 'enc0.args', 'hkpn.args'}.issubset(products): if not {'rr.txt', 'enc0.args', 'hkpn.args'}.issubset(products):
raise RuntimeError('RR cache omits required manifest/templates') raise RuntimeError('RR cache omits required manifest/templates')
@@ -92,7 +106,8 @@ def main():
expected_stages = {'enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0', 'hkpn'} expected_stages = {'enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0', 'hkpn'}
if (not manifest.startswith('D4RR0001\n') or if (not manifest.startswith('D4RR0001\n') or
f'preset {4 if identity["preset"] == "D" else 5}' not in manifest.splitlines() 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): len(stages) != 12 or {fields[1] for fields in stages} != expected_stages or
f'arithmetic {arithmetic}' not in manifest.splitlines()):
raise RuntimeError('Incomplete RR model manifest') raise RuntimeError('Incomplete RR model manifest')
for fields in stages: for fields in stages:
if fields[2] not in products or fields[4] not in products: if fields[2] not in products or fields[4] not in products:
+45
View File
@@ -0,0 +1,45 @@
// Stress the bounded FP32 path against the original sequential FP64 dot.
#define SWIN_BOUNDED_PV
#include "../../kernels/m/swin_common.h"
struct PvParams { uint32_t* output; };
__device__ uint32_t pv_random(uint32_t& state) {
state ^= state << 13; state ^= state >> 17; state ^= state << 5;
return state;
}
extern "C" __global__ void exact_pv_probe(PvParams p) {
const unsigned index = blockIdx.x * 32 + threadIdx.x;
uint32_t state = index + 1;
half_t a[16], b[16];
for (int k = 0; k < 16; ++k) {
uint16_t x = pv_random(state), y = pv_random(state);
x = (x & 0x8000u) | ((x & 0x7fffu) % 0x7c00u);
y = (y & 0x8000u) | ((y & 0x7fffu) % 0x7c00u);
a[k] = __builtin_bit_cast(half_t, x);
b[k] = __builtin_bit_cast(half_t, y);
}
switch (index % 4) {
case 0: break; // Entire finite FP16 exponent range, signed products.
case 1: // Cancellation, including products too small for an FP32 sum.
for (int k = 0; k < 8; ++k) { a[k + 8] = -a[k]; b[k + 8] = b[k]; }
b[15] = (half_t)0.0f;
break;
case 2: // Every finite magnitude with exact midpoint perturbations.
case 3:
for (int k = 0; k < 16; ++k) { a[k] = (half_t)0.0f; b[k] = (half_t)1.0f; }
a[0] = __builtin_bit_cast(half_t, (uint16_t)((index / 4) % 0x7c00u));
{
const uint16_t bits = __builtin_bit_cast(uint16_t, a[0]);
const half_t next = __builtin_bit_cast(half_t, (uint16_t)(bits + (bits < 0x7bffu)));
a[1] = (half_t)((float)next - (float)a[0]);
}
b[1] = (half_t)0.5f;
a[2] = __builtin_bit_cast(half_t, (uint16_t)1);
b[2] = __builtin_bit_cast(half_t, (uint16_t)((index & 1) ? 0x8001u : 1u));
break;
}
double reference = 0.0;
for (int k = 0; k < 16; ++k) reference += (double)a[k] * (double)b[k];
const uint16_t expected = __builtin_bit_cast(uint16_t, (half_t)reference);
const uint16_t actual = __builtin_bit_cast(uint16_t, exact_pv_dot<1>(a, b));
p.output[index] = uint32_t(expected) | (uint32_t(actual) << 16);
}
+56
View File
@@ -0,0 +1,56 @@
// Native D3D12 counterpart of fsr_rr_compose.comp. Keep conversion semantics aligned.
// FSR Ray Regeneration backend, stage 3: the denoised diffuse and specular signals remodulated by
// their albedos, plus the residual the denoiser never saw (particles, what demodulation could not
// carry, sky). The result is render-resolution colour for the Super Sampling stage.
RWTexture2D<float4> denoisedDiffuse : register(u0);
RWTexture2D<float4> denoisedSpecular : register(u1);
RWTexture2D<float4> diffuseAlbedo : register(u2);
RWTexture2D<float4> specularAlbedo : register(u3);
RWTexture2D<float4> residual : register(u4);
RWTexture2D<float4> composed : register(u5);
RWTexture2D<float4> noisyDiffuse : register(u6);
RWTexture2D<float4> noisySpecular : register(u7);
struct FsrRrConstants
{
uint2 renderSize;
float haloLimit; // > 0: each denoised lobe at most this multiple of the brightest noisy input of that lobe around it
uint unused;
};
cbuffer Constants : register(b0) { FsrRrConstants pc; }
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
const int2 px = int2(tid.xy);
if (px.x >= int(pc.renderSize.x) || px.y >= int(pc.renderSize.y))
return;
const float3 da = diffuseAlbedo[px].rgb, sa = specularAlbedo[px].rgb;
float3 diffuse = denoisedDiffuse[px].rgb * da;
float3 specular = denoisedSpecular[px].rgb * sa;
if (pc.haloLimit > 0.0)
{
// The denoiser can carry a thin object's large demodulated signal (dark albedo) across its edge, where this
// texel's albedo turns it into a bright fringe. A denoised value above everything around it in the noisy
// input is such a leak: capped, by luminance (the hue is kept).
const float3 w = float3(0.2126, 0.7152, 0.0722);
float diffuseMax = 0.0, specularMax = 0.0;
for (int dy = -1; dy <= 1; ++dy)
for (int dx = -1; dx <= 1; ++dx)
{
const int2 q = clamp(px + int2(dx, dy), int2(0, 0), int2(pc.renderSize) - 1);
// what the lobe contributes there (the neighbour's own albedo): a leaked demodulated signal
// remodulated by this texel's albedo exceeds all of these
diffuseMax = max(diffuseMax, dot(noisyDiffuse[q].rgb * diffuseAlbedo[q].rgb, w));
specularMax = max(specularMax, dot(noisySpecular[q].rgb * specularAlbedo[q].rgb, w));
}
const float dl = dot(diffuse, w), sl = dot(specular, w);
if (dl > pc.haloLimit * diffuseMax)
diffuse *= pc.haloLimit * diffuseMax / dl;
if (sl > pc.haloLimit * specularMax)
specular *= pc.haloLimit * specularMax / sl;
}
const float3 color = diffuse + specular + residual[px].rgb;
composed[px] = float4(clamp(color, 0.0, 65000.0), 1.0);
}
+195
View File
@@ -0,0 +1,195 @@
// Native D3D12 counterpart of fsr_rr_prepare.comp. Keep conversion semantics aligned.
// FSR Ray Regeneration backend, stage 1: a game's DLSS Ray Reconstruction inputs -> the inputs of
// AMD's FidelityFX denoiser 1.2 (ffx_denoiser.h), at render resolution.
//
// The split of the game's single noisy colour into a diffuse and a specular signal follows the
// DLSS-RR -> FSR-RR adapter of Zach Hembree's OptiScaler branch ffx-denoise-experimental (GPL-3.0,
// 18ac686, FSRDInputConv.hlsl, "Mode 2") and burak113's continuation (5a6adb1): each lobe gets the
// colour in proportion to its albedo and is demodulated by it. That is an approximation - one
// colour cannot be split exactly - and what does not survive demodulation goes to the residual,
// which bypasses the denoiser. Unlike that reference this writes FSR-RR 1.2's contract: signed
// linear depth reconstructed from hardware or linear depth, motion as previous-minus-current UV,
// linear (non-gamma) albedo and the colour before particles as the denoised base.
Texture2D<float4> inColor : register(t0);
Texture2D<float4> inDepth : register(t1);
Texture2D<float4> inMotion : register(t2);
Texture2D<float4> inNormals : register(t3);
Texture2D<float4> inRoughness : register(t4); // the normals again when roughness is packed
Texture2D<float4> inDiffuseAlbedo : register(t5);
Texture2D<float4> inSpecularAlbedo : register(t6);
Texture2D<float4> inSpecularHit : register(t7); // bound to the normals when absent
Texture2D<float4> inDiffuseHit : register(t8); // bound to the normals when absent
Texture2D<float4> inBeforeParticles : register(t9); // bound to the colour when absent
RWTexture2D<float> outLinearDepth : register(u0);
RWTexture2D<float4> outMotion : register(u1);
RWTexture2D<float4> outNormals : register(u2);
RWTexture2D<float4> outDiffuseAlbedo : register(u3);
RWTexture2D<float4> outSpecularAlbedo : register(u4);
RWTexture2D<float4> outDiffuse : register(u5);
RWTexture2D<float4> outSpecular : register(u6);
RWTexture2D<float4> outResidual : register(u7);
// Matrices are uploaded as the row-major, row-vector (v' = vM) float[16] NGX and FFX use. Read as a
// GLSL (column-major) column_major float4x4 that is the transpose, so M * v here is the row-vector product v * M.
struct FsrRrConstants
{
column_major float4x4 clipToView; // inverse(ViewToClipMatrix)
column_major float4x4 viewToPreviousView; // inverse(WorldToView) * previous WorldToView
float4 renderSize; // width, height, 1 / width, 1 / height
float4 motion; // xy: MV texel -> UV delta (MV.Scale / MV resolution); zw: MV texel per render pixel
uint4 colorDepthBase; // colour subrect origin xy, depth subrect origin xy
uint4 motionBaseFlags; // motion subrect origin xy, flags, unused
float4 depthParams; // x: sign of view z in front of the camera, y: sky depth (hardware), z: far marker
float4 detail; // x: share of the colour that bypasses the denoiser (0: none); y: smallest albedo a
// signal is divided by
};
cbuffer Constants : register(b0) { FsrRrConstants pc; }
static const uint kHardwareDepth = 1u;
static const uint kRoughnessPacked = 2u;
static const uint kSpecularHit = 4u;
static const uint kSpecularHitInW = 8u; // DLSSD.SpecularRayDirectionHitDistance: the distance is .w
static const uint kDiffuseHit = 16u;
static const uint kDiffuseHitInW = 32u;
static const uint kNormalsUnorm = 64u; // [0,1]-encoded normals (UNORM formats)
static const uint kBeforeParticles = 128u;
static const uint kInvalidNormalPassthrough = 256u;
static const float kHalfMax = 65000.0;
float3 safe(float3 v)
{
// NaN fails both comparisons and becomes 0
return float3(v.x >= 0.0 ? min(v.x, kHalfMax) : 0.0, v.y >= 0.0 ? min(v.y, kHalfMax) : 0.0,
v.z >= 0.0 ? min(v.z, kHalfMax) : 0.0);
}
// AMD's NormalToOctahedronUv (FidelityFX denoising.md 5.3.1)
float2 octahedron(float3 n)
{
n /= abs(n.x) + abs(n.y) + abs(n.z);
float2 k = float2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);
float2 xy = n.z < 0.0 ? (1.0 - abs(n.yx)) * k : n.xy;
return xy * 0.5 + 0.5;
}
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
const int2 px = int2(tid.xy);
if (px.x >= int(pc.renderSize.x) || px.y >= int(pc.renderSize.y))
return;
const uint flags = pc.motionBaseFlags.z;
const float2 uv = (float2(px) + 0.5) * pc.renderSize.zw;
const float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
// Signed view-space position. Hardware depth goes through the inverse projection, which carries
// the game's handedness and (reversed) depth range; linear depth is the distance along the view
// axis, given its sign by the projection.
const float depth = inDepth.Load(int3(int2(pc.colorDepthBase.zw) + px, 0)).r;
float3 viewPos;
bool sky;
if ((flags & kHardwareDepth) != 0u)
{
const float4 p = mul(pc.clipToView, float4(ndc, depth, 1.0));
viewPos = p.xyz / p.w;
sky = depth == pc.depthParams.y || !(abs(p.w) > 0.0);
}
else
{
const float4 p = mul(pc.clipToView, float4(ndc, 0.5, 1.0));
const float3 ray = p.xyz / p.w;
const float z = depth * pc.depthParams.x;
viewPos = ray * (z / ray.z);
sky = !(depth > 0.0) || depth >= pc.depthParams.z;
}
const float3 color = safe(inColor.Load(int3(int2(pc.colorDepthBase.xy) + px, 0)).rgb);
const float3 base = (flags & kBeforeParticles) != 0u ? safe(inBeforeParticles.Load(int3(px, 0)).rgb) : color;
// Particles (colour after minus before) stay out of the denoiser; signed, so smoke can darken.
const float3 particles = color - base;
float3 diffuseAlbedo = clamp(inDiffuseAlbedo.Load(int3(px, 0)).rgb, 0.0, 1.0);
float3 specularAlbedo = clamp(inSpecularAlbedo.Load(int3(px, 0)).rgb, 0.0, 1.0);
// Energy above 1 is taken from the diffuse lobe first (reference: albedo overshoot clamp).
diffuseAlbedo = max(diffuseAlbedo - max(diffuseAlbedo + specularAlbedo - 1.0, 0.0), 0.0);
const float albedoSum = dot(diffuseAlbedo + specularAlbedo, ((float3)1.0));
sky = sky || !(albedoSum > 3e-3) || any(isnan(viewPos)) || any(isinf(viewPos));
const float4 normalTexel = inNormals.Load(int3(px, 0));
float3 normal = (flags & kNormalsUnorm) != 0u ? normalTexel.xyz * 2.0 - 1.0 : normalTexel.xyz;
const float normalLength = dot(normal, normal);
// A zero/invalid normal is no surface, not an invented world-Z-facing mirror. Alan Wake 2
// supplies zero normals for its sky with finite FP16-max depth, so the far bound alone misses it.
if ((flags & kInvalidNormalPassthrough) != 0u)
sky = sky || !(normalLength > 1e-8) || isnan(normalLength) || isinf(normalLength);
if (sky)
{
// Outside linearDepthBounds: the denoiser passes these texels through, and the residual carries
// the whole colour.
outLinearDepth[px] = float(pc.depthParams.z * 10.0 * pc.depthParams.x);
outMotion[px] = ((float4)0.0);
outNormals[px] = float4(0.5, 0.5, 1.0, 0.0);
outDiffuseAlbedo[px] = ((float4)0.0);
outSpecularAlbedo[px] = ((float4)0.0);
outDiffuse[px] = ((float4)0.0);
outSpecular[px] = ((float4)0.0);
outResidual[px] = float4(color, 0.0);
return;
}
outLinearDepth[px] = float(viewPos.z);
// 2.5D motion: DLSS motion vectors point from this pixel to where it was (previous - current), in
// the MV texture's pixels. The depth delta is the camera's: the previous view's z of this point
// (moving geometry is not reprojected; there is no previous depth for it).
const int2 mvTexel = int2(pc.motionBaseFlags.xy) + int2(float2(px) * pc.motion.zw);
const float2 mv = inMotion.Load(int3(mvTexel, 0)).xy * pc.motion.xy;
const float4 previous = mul(pc.viewToPreviousView, float4(viewPos, 1.0));
outMotion[px] = float4(mv, previous.z / previous.w - viewPos.z, 0.0);
normal = dot(normal, normal) > 1e-8 ? normalize(normal) : float3(0.0, 0.0, 1.0);
const float roughness = clamp((flags & kRoughnessPacked) != 0u ? normalTexel.w : inRoughness.Load(int3(px, 0)).r,
0.0, 1.0);
// material type 0 (the default material) everywhere
outNormals[px] = float4(octahedron(normal), roughness, 0.0);
outDiffuseAlbedo[px] = float4(diffuseAlbedo, 0.0);
outSpecularAlbedo[px] = float4(specularAlbedo, 0.0);
// Split by albedo and demodulate (reference "Mode 2"). Per channel, so a tinted metal keeps its tint.
const float3 total = diffuseAlbedo + specularAlbedo;
const float3 specularShare = specularAlbedo / max(total, ((float3)1e-4));
const float3 specularColor = base * specularShare;
const float3 diffuseColor = base - specularColor;
// A dark albedo would turn light it did not shape (fog, sky light, the background at a thin object's edge) into
// a huge signal that the denoiser clips (fog lost) or spreads (bright fringes): no albedo below the floor
// divides a signal, and what the division cannot carry stays in the residual.
float3 diffuse = safe(diffuseColor / max(diffuseAlbedo, ((float3)pc.detail.y)));
float3 specular = safe(specularColor / max(specularAlbedo, ((float3)pc.detail.y)));
if (pc.detail.x > 0.0)
{
// Fine detail the denoiser treats as noise (hair, water ripples): part of the colour goes around it,
// through the residual.
const float denoised = 1.0 - clamp(pc.detail.x, 0.0, 1.0);
diffuse *= denoised;
specular *= denoised;
}
const float3 remodulated = diffuse * diffuseAlbedo + specular * specularAlbedo;
// A: the ray hit distance for an indirect signal (ignored, but non-negative, for a direct one)
float specularHit = 0.0, diffuseHit = 0.0;
if ((flags & kSpecularHit) != 0u)
{
const float4 hit = inSpecularHit.Load(int3(px, 0));
specularHit = clamp((flags & kSpecularHitInW) != 0u ? hit.w : hit.x, 0.0, kHalfMax);
}
if ((flags & kDiffuseHit) != 0u)
{
const float4 hit = inDiffuseHit.Load(int3(px, 0));
diffuseHit = clamp((flags & kDiffuseHitInW) != 0u ? hit.w : hit.x, 0.0, kHalfMax);
}
outDiffuse[px] = float4(diffuse, diffuseHit);
outSpecular[px] = float4(specular, specularHit);
outResidual[px] = float4(base - remodulated + particles, 0.0);
}
+225 -103
View File
@@ -1,4 +1,6 @@
// FSR Ray Regeneration as the native Windows Ray Reconstruction backend (experimental). See fsr_rr_backend.h. // FSR Ray Regeneration as the native Windows Ray Reconstruction backend (experimental). See fsr_rr_backend.h.
// RDNA4 records conversion, AMD denoising and composition on the game's native D3D12 device.
// The compatibility route described below remains the RDNA3 default.
// //
// Data path of one evaluation (every hand-off is CPU-synchronized; no vkd3d-proton patch is needed): // Data path of one evaluation (every hand-off is CPU-synchronized; no vkd3d-proton patch is needed):
// game queue : guides -> shared packet (D3D12 copies, as the native DLSS backend), signal, CPU waits // game queue : guides -> shared packet (D3D12 copies, as the native DLSS backend), signal, CPU waits
@@ -11,9 +13,12 @@
#include "../d4r_fsr_rr.h" #include "../d4r_fsr_rr.h"
#include "../d4r_vkd3d_interop.h" #include "../d4r_vkd3d_interop.h"
#include "fsr_rr_passes.h" #include "fsr_rr_passes.h"
#include "fsr_rr_native_passes.h"
#include "fsr_rr_route.h"
#include "d3d12_command_hooks.h" #include "d3d12_command_hooks.h"
#include <volk.h> #include <volk.h>
#include "rr_bridge_replay.h" #include "rr_bridge_replay.h"
#include "rr_input_formats.h"
#include <vulkan/vulkan_win32.h> #include <vulkan/vulkan_win32.h>
#include <dxgi1_4.h> #include <dxgi1_4.h>
#include <chrono> #include <chrono>
@@ -134,35 +139,9 @@ void multiply(const double (&a)[16], const double (&b)[16], double (&out)[16]) {
double sum = 0; for (int k = 0; k < 4; ++k) sum += a[r * 4 + k] * b[k * 4 + c]; out[r * 4 + c] = sum; double sum = 0; for (int k = 0; k < 4; ++k) sum += a[r * 4 + k] * b[k * 4 + c]; out[r * 4 + c] = sum;
} }
} }
bool unorm(VkFormat format) {
return format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_A2B10G10R10_UNORM_PACK32;
}
size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; } size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; }
// Same sampled-input formats as the native backend: the texel values reach Vulkan unchanged. // Same sampled-input formats as the native backend (rr_input_formats.h).
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { return guide_format(format, plane, "FSR RR"); }
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 FSR RR sampled input format");
}
DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) { DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) {
const auto desc = resource_desc(texture); const auto desc = resource_desc(texture);
if (plane != Snapshot::Depth) return desc.Format; if (plane != Snapshot::Depth) return desc.Format;
@@ -213,8 +192,14 @@ FsrConfig FsrConfig::from_environment() {
config.cacheDir = env_wide("D4R_FSR_CACHE_DIR"); config.cacheDir = env_wide("D4R_FSR_CACHE_DIR");
return config; return config;
} }
bool FsrConfig::use_native_device() {
return native_fsr_route(D4R_TARGET_ARCH, std::getenv("D4R_FSR_RR_DEVICE"));
}
struct FsrBackend::Impl { struct FsrBackend::Impl {
bool nativeD3d12 = FsrConfig::use_native_device();
NativeFsrPasses nativePasses;
std::array<NativeFsrPasses::Guide, 11> nativeGuides{};
Creation creation; Creation creation;
rr_bridge::Layout layout; rr_bridge::Layout layout;
ComPtr<ID3D12Device> d3d; // the game's device ComPtr<ID3D12Device> d3d; // the game's device
@@ -226,6 +211,7 @@ struct FsrBackend::Impl {
Handle event; Handle event;
uint64_t sequence = 0; uint64_t sequence = 0;
bool pending = false, failed = false; bool pending = false, failed = false;
std::unique_ptr<Snapshot> retained;
// --- private vkd3d-proton side --- // --- private vkd3d-proton side ---
HMODULE core = nullptr; HMODULE core = nullptr;
ComPtr<ID3D12Device> vdev; ComPtr<ID3D12Device> vdev;
@@ -306,6 +292,26 @@ struct FsrBackend::Impl {
if ((creation.flags & 3u) != 3u) throw std::runtime_error("FSR RR requires HDR and render-resolution motion vectors"); if ((creation.flags & 3u) != 3u) throw std::runtime_error("FSR RR requires HDR and render-resolution motion vectors");
if (creation.flags & 4u) throw std::runtime_error("FSR RR does not convert jittered motion vectors"); if (creation.flags & 4u) throw std::runtime_error("FSR RR does not convert jittered motion vectors");
if (creation.quality < 0 || creation.quality > 5) throw std::runtime_error("Unsupported RR quality mode"); if (creation.quality < 0 || creation.quality > 5) throw std::runtime_error("Unsupported RR quality mode");
if (nativeD3d12) {
// AMD sees the game's real D3D12 device and its real driver extension.
// No vkd3d core, amdxc shim, external packet or Vulkan device is loaded.
std::fprintf(stderr, "D4R_FSR_RR_DEVICE native-d3d12 target=%s cross_api_copies=0\n", D4R_TARGET_ARCH);
vdev = d3d;
vevent.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!vevent.value) throw std::runtime_error("FSR RR native event creation failed");
dx(vdev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&vfence)), "FSR RR native fence");
dx(vdev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&vallocator)), "FSR RR native allocator");
dx(vdev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, vallocator.Get(), nullptr, IID_PPV_ARGS(&vlist)), "FSR RR native list");
dx(vlist->Close(), "FSR RR native initial close");
if (!runtime.load(config.denoiserDll)) throw std::runtime_error(std::string("FSR RR denoiser DLL: ") + fsr_rr_stage_name(runtime.stage));
if (!runtime.query_providers(vdev.Get())) throw std::runtime_error(std::string("FSR RR native D3D12 provider: ") + fsr_rr_stage_name(runtime.stage));
nativePasses.initialize(vdev.Get());
create_intermediates();
create_context();
std::fprintf(stderr, "FSR_RR provider \"%s\" on native D3D12\n", runtime.providerName.c_str());
return;
}
std::fprintf(stderr, "D4R_FSR_RR_DEVICE vkd3d target=%s\n", D4R_TARGET_ARCH);
if (config.vkd3dCore.empty() || config.denoiserDll.empty() || config.amdxcShim.empty()) if (config.vkd3dCore.empty() || config.denoiserDll.empty() || config.amdxcShim.empty())
throw std::runtime_error("FSR RR needs D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM"); throw std::runtime_error("FSR RR needs D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM");
// --- the game-device packet, fence and copy lists (as the native backend) --- // --- the game-device packet, fence and copy lists (as the native backend) ---
@@ -337,7 +343,23 @@ struct FsrBackend::Impl {
void start_vkd3d() { void start_vkd3d() {
// vkd3d-proton's own environment: the dxil-spirv RDNA3 WMMA/FP8 emulation, and a cache for its shaders. // vkd3d-proton's own environment: the dxil-spirv RDNA3 WMMA/FP8 emulation, and a cache for its shaders.
SetEnvironmentVariableA("DXIL_SPIRV_CONFIG", "wmma_rdna3_workaround"); // gfx12 uses the native FP8 matrix layout. Applying the gfx11 layout rewrite
// there also forces FP16 RTZ in dxil-spirv. Preserve explicit diagnostic overrides.
// Read the Windows environment: the C runtime's getenv snapshot does not
// reflect SetEnvironmentVariable calls made by other DLLs in this process.
DWORD configLength = GetEnvironmentVariableA("DXIL_SPIRV_CONFIG", nullptr, 0);
if (!configLength && GetLastError() == ERROR_ENVVAR_NOT_FOUND &&
std::string(D4R_TARGET_ARCH).rfind("gfx11", 0) == 0) {
if (!SetEnvironmentVariableA("DXIL_SPIRV_CONFIG", "wmma_rdna3_workaround"))
throw std::runtime_error("FSR RR shader compiler configuration failed");
configLength = GetEnvironmentVariableA("DXIL_SPIRV_CONFIG", nullptr, 0);
}
if (log_enabled()) {
std::vector<char> compilerConfig(configLength ? configLength : 1, '\0');
GetEnvironmentVariableA("DXIL_SPIRV_CONFIG", compilerConfig.data(), DWORD(compilerConfig.size()));
std::fprintf(stderr, "FSR_RR compiler: target=%s DXIL_SPIRV_CONFIG=%s\n",
D4R_TARGET_ARCH, compilerConfig[0] ? compilerConfig.data() : "<default>");
}
if (!config.cacheDir.empty() && !std::getenv("VKD3D_SHADER_CACHE_PATH")) { if (!config.cacheDir.empty() && !std::getenv("VKD3D_SHADER_CACHE_PATH")) {
CreateDirectoryW(config.cacheDir.c_str(), nullptr); CreateDirectoryW(config.cacheDir.c_str(), nullptr);
SetEnvironmentVariableW(L"VKD3D_SHADER_CACHE_PATH", config.cacheDir.c_str()); SetEnvironmentVariableW(L"VKD3D_SHADER_CACHE_PATH", config.cacheDir.c_str());
@@ -435,6 +457,7 @@ struct FsrBackend::Impl {
t.desc.SampleDesc.Count = 1; t.desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; t.desc.SampleDesc.Count = 1; t.desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
dx(vdev->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &t.desc, t.state, nullptr, IID_PPV_ARGS(&t.resource)), "FSR RR intermediate texture"); dx(vdev->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &t.desc, t.state, nullptr, IID_PPV_ARGS(&t.resource)), "FSR RR intermediate texture");
if (nativeD3d12) return;
UINT64 handle = 0, offset = 0; VkFormat format = VK_FORMAT_UNDEFINED; UINT64 handle = 0, offset = 0; VkFormat format = VK_FORMAT_UNDEFINED;
dx(interop->GetVulkanResourceInfo1(t.resource.Get(), &handle, &offset, &format), "FSR RR intermediate Vulkan info"); dx(interop->GetVulkanResourceInfo1(t.resource.Get(), &handle, &offset, &format), "FSR RR intermediate Vulkan info");
if (!handle) throw std::runtime_error("FSR RR intermediate has no Vulkan image"); if (!handle) throw std::runtime_error("FSR RR intermediate has no Vulkan image");
@@ -522,10 +545,9 @@ struct FsrBackend::Impl {
if (superSampling && (frame.renderWidth < srRenderLimits[0] || frame.renderHeight < srRenderLimits[1] || if (superSampling && (frame.renderWidth < srRenderLimits[0] || frame.renderHeight < srRenderLimits[1] ||
frame.renderWidth > srRenderLimits[2] || frame.renderHeight > srRenderLimits[3])) frame.renderWidth > srRenderLimits[2] || frame.renderHeight > srRenderLimits[3]))
throw std::runtime_error("FSR RR render size is outside the Super Sampling dynamic render range"); throw std::runtime_error("FSR RR render size is outside the Super Sampling dynamic render range");
if (frame.exposureScale != 1 || frame.depthType || frame.normalsType || frame.roughnessType || frame.diffuseType || frame.specularType) // Like the Linux FSR backend: the exposure scale goes to the Super Sampling stage (it receives
throw std::runtime_error("FSR RR guide encoding or exposure scale requires conversion"); // frame.exposureScale), and the NGX guide encoding hints (DLSS.*.Type) are not read.
if (frame.resources[Snapshot::Disocclusion] || frame.resources[Snapshot::Subsurface]) if (frame.exposureScale != 1 && !superSampling) throw std::runtime_error("FSR RR exposure scale needs the Super Sampling stage");
throw std::runtime_error("FSR RR does not use the disocclusion or subsurface guides");
if (!superSampling && (frame.renderWidth != creation.outWidth || frame.renderHeight != creation.outHeight)) if (!superSampling && (frame.renderWidth != creation.outWidth || frame.renderHeight != creation.outHeight))
throw std::runtime_error("FSR RR upscaling needs a Super Sampling stage (none is installed) or output size == render size"); throw std::runtime_error("FSR RR upscaling needs a Super Sampling stage (none is installed) or output size == render size");
for (size_t i = 0; i < Snapshot::Count; ++i) { for (size_t i = 0; i < Snapshot::Count; ++i) {
@@ -551,7 +573,7 @@ struct FsrBackend::Impl {
frame.origins[i][0] > desc.Width || width > desc.Width - frame.origins[i][0] || 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]) frame.origins[i][1] > desc.Height || height > desc.Height - frame.origins[i][1])
throw std::runtime_error("Unsupported FSR RR texture format or subrect (FSR-only output must be RGBA16F or RGBA32F)"); throw std::runtime_error("Unsupported FSR RR texture format or subrect (FSR-only output must be RGBA16F or RGBA32F)");
if (i == Snapshot::Output && !superSampling && desc.Format == DXGI_FORMAT_R32G32B32A32_FLOAT) { if (!nativeD3d12 && i == Snapshot::Output && !superSampling && desc.Format == DXGI_FORMAT_R32G32B32A32_FLOAT) {
VkFormatProperties source{}, destination{}; VkFormatProperties source{}, destination{};
api.formatProperties(vkPhysical, VK_FORMAT_R16G16B16A16_SFLOAT, &source); api.formatProperties(vkPhysical, VK_FORMAT_R16G16B16A16_SFLOAT, &source);
api.formatProperties(vkPhysical, VK_FORMAT_R32G32B32A32_SFLOAT, &destination); api.formatProperties(vkPhysical, VK_FORMAT_R32G32B32A32_SFLOAT, &destination);
@@ -593,6 +615,14 @@ struct FsrBackend::Impl {
if (WaitForSingleObject(vevent.value, 60000) != WAIT_OBJECT_0) throw std::runtime_error("FSR RR vkd3d completion timeout"); if (WaitForSingleObject(vevent.value, 60000) != WAIT_OBJECT_0) throw std::runtime_error("FSR RR vkd3d completion timeout");
if (vfence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("FSR RR vkd3d device removed"); if (vfence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("FSR RR vkd3d device removed");
} }
void prepare_native_guides(const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
for (size_t i : {0, 1, 2, 3, 4, 5, 6, 7, 9, 10}) {
if (!layout.planes[i].enabled) continue;
const size_t index = resource_index(i);
nativePasses.copy_guide(vlist.Get(), frame.resources[index].Get(), access[index], index == Snapshot::Depth,
layout.rw, layout.rh, creation.width, creation.height, frame.origins[index][0], frame.origins[index][1], nativeGuides[i]);
}
}
void wait_game(ID3D12CommandQueue* q, uint64_t value) { void wait_game(ID3D12CommandQueue* q, uint64_t value) {
dx(q->Signal(fence.Get(), value), "FSR RR game signal"); dx(q->Signal(fence.Get(), value), "FSR RR game signal");
dx(fence->SetEventOnCompletion(value, event.value), "FSR RR game completion event"); dx(fence->SetEventOnCompletion(value, event.value), "FSR RR game completion event");
@@ -618,6 +648,7 @@ struct FsrBackend::Impl {
void evaluate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) { void evaluate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
validate(q, frame, access); validate(q, frame, access);
retained = std::make_unique<Snapshot>(frame);
failed = true; failed = true;
const auto begin = Clock::now(); const auto begin = Clock::now();
layout.rw = frame.renderWidth; layout.rh = frame.renderHeight; layout.rw = frame.renderWidth; layout.rh = frame.renderHeight;
@@ -634,6 +665,27 @@ struct FsrBackend::Impl {
layout.outputFormat = floatOutput ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_R16G16B16A16_SFLOAT; layout.outputFormat = floatOutput ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_R16G16B16A16_SFLOAT;
const auto outputFormat = floatOutput ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT; const auto outputFormat = floatOutput ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT;
commands::InternalScope internal; commands::InternalScope internal;
if (nativeD3d12) {
vqueue = q;
if (log_enabled() && !stamps) {
D3D12_QUERY_HEAP_DESC heap{}; heap.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP; heap.Count = 4;
if (SUCCEEDED(vdev->CreateQueryHeap(&heap, IID_PPV_ARGS(&stamps))) &&
SUCCEEDED(q->GetTimestampFrequency(&stampFrequency)) && stampFrequency)
stampReadback = make_buffer(vdev.Get(), 4 * sizeof(UINT64), D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
else stamps.Reset();
}
pending = true; queue = q;
run_vkd3d(frame, &access);
if (superSampling) {
const auto upscaleStart = Clock::now();
superSampling(q, textures[Composed].resource.Get(), frame, access);
if (log_enabled()) std::fprintf(stderr, "FSR_RR super sampling submitted in %.2f ms\n", since(upscaleStart));
}
pending = false; failed = false; queue.Reset(); retained.reset();
if (log_enabled()) std::fprintf(stderr, "FSR_RR native frame %u: %ux%u total %.2f ms\n", frameIndex, layout.rw, layout.rh, since(begin));
++frameIndex;
return;
}
// --- 1. the game's guides into the shared packet --- // --- 1. the game's guides into the shared packet ---
dx(producerAllocator->Reset(), "FSR RR producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "FSR RR producer reset"); dx(producerAllocator->Reset(), "FSR RR producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "FSR RR producer reset");
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST); transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
@@ -695,19 +747,20 @@ struct FsrBackend::Impl {
superSampling(q, denoised.Get(), frame, access); superSampling(q, denoised.Get(), frame, access);
if (log_enabled()) std::fprintf(stderr, "FSR_RR super sampling submitted in %.2f ms\n", since(upscaleStart)); if (log_enabled()) std::fprintf(stderr, "FSR_RR super sampling submitted in %.2f ms\n", since(upscaleStart));
} }
pending = false; failed = false; queue.Reset(); pending = false; failed = false; queue.Reset(); retained.reset();
if (log_enabled()) if (log_enabled())
std::fprintf(stderr, "FSR_RR frame %u: %ux%u produce %.2f ms, vkd3d (convert+denoise+compose) %.2f ms, total %.2f ms\n", std::fprintf(stderr, "FSR_RR frame %u: %ux%u produce %.2f ms, vkd3d (convert+denoise+compose) %.2f ms, total %.2f ms\n",
frameIndex, layout.rw, layout.rh, producedMs, vkd3dMs, since(begin)); frameIndex, layout.rw, layout.rh, producedMs, vkd3dMs, since(begin));
++frameIndex; ++frameIndex;
} }
void run_vkd3d(const Snapshot& frame) { void run_vkd3d(const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>* access = nullptr) {
const auto& s = frame.settings; const auto& s = frame.settings;
const uint32_t rw = layout.rw, rh = layout.rh; const uint32_t rw = layout.rw, rh = layout.rh;
// which game planes exist decides the context's specular signal once // which hit distances the game passes decides the context's signals once (as on Linux): a lobe with a
const bool specularHit = layout.planes[7].enabled; // ray length is an indirect signal. Layout plane 9 is the Subsurface slot, carrying the diffuse hit.
const uint32_t wanted = FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE | const bool specularHit = layout.planes[7].enabled, diffuseHit = frame.diffuseHit && layout.planes[9].enabled;
const uint32_t wanted = (diffuseHit ? FFX_DENOISER_SIGNAL_INDIRECT_DIFFUSE : FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE) |
(specularHit ? FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR : FFX_DENOISER_SIGNAL_DIRECT_SPECULAR); (specularHit ? FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR : FFX_DENOISER_SIGNAL_DIRECT_SPECULAR);
if (wanted != signals) { if (wanted != signals) {
if (frameIndex == 0) { if (frameIndex == 0) {
@@ -715,9 +768,13 @@ struct FsrBackend::Impl {
const ffxReturnCode_t created = fsr_rr_create_context(runtime, vdev.Get(), creation.width, creation.height, signals, 0, context); const ffxReturnCode_t created = fsr_rr_create_context(runtime, vdev.Get(), creation.width, creation.height, signals, 0, context);
if (created != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxCreateContext failed: ") + fsr_rr_return_name(created)); if (created != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxCreateContext failed: ") + fsr_rr_return_name(created));
apply_denoiser_keys(); apply_denoiser_keys();
std::fprintf(stderr, "d4r: FSR RR diffuse %s, specular %s\n",
diffuseHit ? (frame.diffuseHitInW ? "indirect (hit distance in ray direction w)" : "indirect (hit distance)") : "direct (no hit distance)",
specularHit ? (frame.specularHitInW ? "indirect (hit distance in ray direction w)" : "indirect (hit distance)") : "direct (no hit distance)");
} else throw std::runtime_error("FSR RR: the game's hit-distance guide changed after the denoiser was created (recreate the feature)"); } else throw std::runtime_error("FSR RR: the game's hit-distance guide changed after the denoiser was created (recreate the feature)");
} }
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 10}) if (layout.planes[plane].enabled) ensure_staging(plane); if (!nativeD3d12)
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 9, 10}) if (layout.planes[plane].enabled) ensure_staging(plane);
double clipToView[16], viewToWorld[16]; double clipToView[16], viewToWorld[16];
if (!invert(s.viewToClip, clipToView) || !invert(s.worldToView, viewToWorld)) throw std::runtime_error("FSR RR camera matrix is not invertible"); if (!invert(s.viewToClip, clipToView) || !invert(s.worldToView, viewToWorld)) throw std::runtime_error("FSR RR camera matrix is not invertible");
const bool reset = s.reset || !havePrevious; const bool reset = s.reset || !havePrevious;
@@ -731,8 +788,9 @@ struct FsrBackend::Impl {
uint32_t flags = 0; uint32_t flags = 0;
if (s.hardwareDepth) flags |= kFsrRrHardwareDepth; if (s.hardwareDepth) flags |= kFsrRrHardwareDepth;
if (s.roughnessPacked) flags |= kFsrRrRoughnessPacked; if (s.roughnessPacked) flags |= kFsrRrRoughnessPacked;
if (specularHit) flags |= kFsrRrSpecularHit; if (specularHit) flags |= kFsrRrSpecularHit | (frame.specularHitInW ? kFsrRrSpecularHitInW : 0u);
if (unorm(layout.planes[3].format)) flags |= kFsrRrNormalsUnorm; if (diffuseHit) flags |= kFsrRrDiffuseHit | (frame.diffuseHitInW ? kFsrRrDiffuseHitInW : 0u);
if (unorm_guide(layout.planes[3].format)) flags |= kFsrRrNormalsUnorm;
if (layout.planes[10].enabled) flags |= kFsrRrBeforeParticles; if (layout.planes[10].enabled) flags |= kFsrRrBeforeParticles;
flags |= kFsrRrInvalidNormalPassthrough; flags |= kFsrRrInvalidNormalPassthrough;
c.motionBaseFlags[2] = flags; c.motionBaseFlags[2] = flags;
@@ -748,39 +806,54 @@ struct FsrBackend::Impl {
if (t.resource) { transition(vlist.Get(), t.resource.Get(), t.state, state); t.state = state; } if (t.resource) { transition(vlist.Get(), t.resource.Get(), t.state, state); t.state = state; }
}; };
for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
VkCommandBuffer command = VK_NULL_HANDLE;
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin");
// the packet comes in from the game's device
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = packetBuffer; acquire.size = VK_WHOLE_SIZE;
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = vkFamily;
acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 10}) {
if (!layout.planes[plane].enabled) continue;
const auto& st = staging[plane];
VkImageMemoryBarrier toCopy{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toCopy.image = st.image;
toCopy.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; toCopy.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toCopy.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toCopy.srcQueueFamilyIndex = toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toCopy.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toCopy);
VkBufferImageCopy region{}; region.bufferOffset = layout.planes[plane].offset;
region.bufferRowLength = uint32_t(layout.planes[plane].pitch / layout.planes[plane].bytes);
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.imageExtent = {rw, rh, 1};
api.copyBufferToImage(command, packetBuffer, st.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier toRead = toCopy; toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toRead);
}
auto sampled = [&](size_t plane) { return D4rFsrRrImage{staging[plane].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; };
const bool hasHit = layout.planes[7].enabled, hasParticles = layout.planes[10].enabled;
D4rFsrRrImage images[D4rFsrRrPasses::kPrepareInputs + D4rFsrRrPasses::kPrepareOutputs] = {
sampled(0), sampled(2), sampled(1), sampled(3), sampled(4), sampled(5), sampled(6),
hasHit ? sampled(7) : sampled(3), sampled(3), hasParticles ? sampled(10) : sampled(0)};
static const int targets[D4rFsrRrPasses::kPrepareOutputs] = {LinearDepth, MotionT, NormalsT, DiffuseAlbedo, SpecularAlbedo, DiffuseT, SpecularT, Residual}; static const int targets[D4rFsrRrPasses::kPrepareOutputs] = {LinearDepth, MotionT, NormalsT, DiffuseAlbedo, SpecularAlbedo, DiffuseT, SpecularT, Residual};
for (uint32_t i = 0; i < D4rFsrRrPasses::kPrepareOutputs; ++i) VkCommandBuffer command = VK_NULL_HANDLE;
images[D4rFsrRrPasses::kPrepareInputs + i] = {textures[targets[i]].view, textures[targets[i]].layout}; VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
passes.record_prepare(command, images, c, rw, rh); if (nativeD3d12) {
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end"); prepare_native_guides(frame, *access);
ID3D12Resource* resources[18]{}; DXGI_FORMAT formats[18]{};
const size_t planes[10] = {0, 2, 1, 3, 4, 5, 6, specularHit ? 7u : 3u,
diffuseHit ? 9u : 3u, layout.planes[10].enabled ? 10u : 0u};
for (size_t i = 0; i < 10; ++i) {
resources[i] = nativeGuides[planes[i]].resource.Get(); formats[i] = nativeGuides[planes[i]].format;
}
for (size_t i = 0; i < 8; ++i) {
resources[10 + i] = textures[targets[i]].resource.Get(); formats[10 + i] = textures[targets[i]].desc.Format;
}
nativePasses.record(vlist.Get(), nativePasses.prepare, &c, resources, formats, rw, rh);
} else {
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin");
// the packet comes in from the game's device
acquire.buffer = packetBuffer; acquire.size = VK_WHOLE_SIZE;
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = vkFamily;
acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 9, 10}) {
if (!layout.planes[plane].enabled) continue;
const auto& st = staging[plane];
VkImageMemoryBarrier toCopy{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toCopy.image = st.image;
toCopy.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; toCopy.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toCopy.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toCopy.srcQueueFamilyIndex = toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toCopy.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toCopy);
VkBufferImageCopy region{}; region.bufferOffset = layout.planes[plane].offset;
region.bufferRowLength = uint32_t(layout.planes[plane].pitch / layout.planes[plane].bytes);
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.imageExtent = {rw, rh, 1};
api.copyBufferToImage(command, packetBuffer, st.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier toRead = toCopy; toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toRead);
}
auto sampled = [&](size_t plane) { return D4rFsrRrImage{staging[plane].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; };
const bool hasHit = layout.planes[7].enabled, hasParticles = layout.planes[10].enabled;
D4rFsrRrImage images[D4rFsrRrPasses::kPrepareInputs + D4rFsrRrPasses::kPrepareOutputs] = {
sampled(0), sampled(2), sampled(1), sampled(3), sampled(4), sampled(5), sampled(6),
hasHit ? sampled(7) : sampled(3), diffuseHit ? sampled(9) : sampled(3), hasParticles ? sampled(10) : sampled(0)};
for (uint32_t i = 0; i < D4rFsrRrPasses::kPrepareOutputs; ++i)
images[D4rFsrRrPasses::kPrepareInputs + i] = {textures[targets[i]].view, textures[targets[i]].layout};
passes.record_prepare(command, images, c, rw, rh);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end");
}
stamp(1); stamp(1);
// --- AMD FSR Ray Regeneration (D3D12 on vkd3d-proton) --- // --- AMD FSR Ray Regeneration (D3D12 on vkd3d-proton) ---
@@ -811,9 +884,13 @@ struct FsrBackend::Impl {
desc.frameIndex = frameIndex; desc.frameIndex = frameIndex;
desc.flags = uint32_t(FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO) | (reset ? uint32_t(FFX_DENOISER_DISPATCH_RESET) : 0u); desc.flags = uint32_t(FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO) | (reset ? uint32_t(FFX_DENOISER_DISPATCH_RESET) : 0u);
ffxDispatchDescDenoiserDirectDiffuse directDiffuse = {}; ffxDispatchDescDenoiserDirectSpecular directSpecular = {}; ffxDispatchDescDenoiserDirectDiffuse directDiffuse = {}; ffxDispatchDescDenoiserDirectSpecular directSpecular = {};
ffxDispatchDescDenoiserIndirectSpecular indirectSpecular = {}; ffxDispatchDescDenoiserIndirectDiffuse indirectDiffuse = {}; ffxDispatchDescDenoiserIndirectSpecular indirectSpecular = {};
directDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE; FfxApiDenoiserSignal* diffuseSignal; ffxApiHeader* diffuseHeader;
FfxApiDenoiserSignal* diffuseSignal = &directDiffuse.signal; if (diffuseHit) {
indirectDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE; diffuseSignal = &indirectDiffuse.signal; diffuseHeader = &indirectDiffuse.header;
} else {
directDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE; diffuseSignal = &directDiffuse.signal; diffuseHeader = &directDiffuse.header;
}
FfxApiDenoiserSignal* specularSignal; ffxApiHeader* specularHeader; FfxApiDenoiserSignal* specularSignal; ffxApiHeader* specularHeader;
if (specularHit) { if (specularHit) {
indirectSpecular.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR; specularSignal = &indirectSpecular.signal; specularHeader = &indirectSpecular.header; indirectSpecular.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR; specularSignal = &indirectSpecular.signal; specularHeader = &indirectSpecular.header;
@@ -822,7 +899,7 @@ struct FsrBackend::Impl {
} }
diffuseSignal->input = ffx(DiffuseT, ffxRead); diffuseSignal->output = ffx(DiffuseOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); diffuseSignal->input = ffx(DiffuseT, ffxRead); diffuseSignal->output = ffx(DiffuseOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
specularSignal->input = ffx(SpecularT, ffxRead); specularSignal->output = ffx(SpecularOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); specularSignal->input = ffx(SpecularT, ffxRead); specularSignal->output = ffx(SpecularOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
desc.header.pNext = &directDiffuse.header; directDiffuse.header.pNext = specularHeader; desc.header.pNext = diffuseHeader; diffuseHeader->pNext = specularHeader;
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header); const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
if (dispatched != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxDispatch failed: ") + fsr_rr_return_name(dispatched)); if (dispatched != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxDispatch failed: ") + fsr_rr_return_name(dispatched));
D3D12_RESOURCE_BARRIER uav{}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; vlist->ResourceBarrier(1, &uav); D3D12_RESOURCE_BARRIER uav{}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; vlist->ResourceBarrier(1, &uav);
@@ -830,30 +907,62 @@ struct FsrBackend::Impl {
// --- composition, then the composed image into the packet's output region --- // --- composition, then the composed image into the packet's output region ---
for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
const D4rFsrRrImage compose[D4rFsrRrPasses::kComposeImages] = { if (nativeD3d12) {
{textures[DiffuseOut].view, textures[DiffuseOut].layout}, {textures[SpecularOut].view, textures[SpecularOut].layout}, ID3D12Resource* resources[8]{}; DXGI_FORMAT formats[8]{};
{textures[DiffuseAlbedo].view, textures[DiffuseAlbedo].layout}, {textures[SpecularAlbedo].view, textures[SpecularAlbedo].layout}, const int indices[8] = {DiffuseOut, SpecularOut, DiffuseAlbedo, SpecularAlbedo, Residual, Composed, DiffuseT, SpecularT};
{textures[Residual].view, textures[Residual].layout}, {textures[Composed].view, textures[Composed].layout}, for (size_t i = 0; i < 8; ++i) {
{textures[DiffuseT].view, textures[DiffuseT].layout}, {textures[SpecularT].view, textures[SpecularT].layout}}; resources[i] = textures[indices[i]].resource.Get(); formats[i] = textures[indices[i]].desc.Format;
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin (compose)"); }
passes.record_compose(command, compose, rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f)); struct { uint32_t width, height; float haloLimit; uint32_t unused; } composeConstants =
int copiedImage = Composed; {rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f), 0};
if (layout.outputBytes == 16) { nativePasses.record(vlist.Get(), nativePasses.compose, &composeConstants, resources, formats, rw, rh);
// Preserve the denoiser/composition's half arithmetic; widen only the completed output on the GPU. int copiedImage = Composed;
VkImageBlit region{}; region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; if (layout.outputBytes == 16) {
region.srcOffsets[1] = region.dstOffsets[1] = {int32_t(rw), int32_t(rh), 1}; setState(Composed, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
api.blitImage(command, textures[Composed].image, textures[Composed].layout, ID3D12Resource* widening[] = {textures[Composed].resource.Get(), textures[Widened].resource.Get()};
textures[Widened].image, textures[Widened].layout, 1, &region, VK_FILTER_NEAREST); DXGI_FORMAT wideningFormats[] = {textures[Composed].desc.Format, textures[Widened].desc.Format};
passes.full_barrier(command); const uint32_t extent[2] = {rw, rh};
copiedImage = Widened; nativePasses.record(vlist.Get(), nativePasses.widen, extent, widening, wideningFormats, rw, rh);
copiedImage = Widened;
}
if (superSampling) {
setState(Composed, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
} else {
setState(copiedImage, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto* output = frame.resources[Snapshot::Output].Get();
texture_copy_barrier(vlist.Get(), output, (*access)[Snapshot::Output], true, false);
auto source = rr_bridge::D3D12Frames::texture(textures[copiedImage].resource.Get());
auto destination = rr_bridge::D3D12Frames::texture(output);
D3D12_BOX box{0, 0, 0, rw, rh, 1};
vlist->CopyTextureRegion(&destination, frame.origins[Snapshot::Output][0], frame.origins[Snapshot::Output][1], 0, &source, &box);
texture_copy_barrier(vlist.Get(), output, (*access)[Snapshot::Output], true, true);
}
} else {
const D4rFsrRrImage compose[D4rFsrRrPasses::kComposeImages] = {
{textures[DiffuseOut].view, textures[DiffuseOut].layout}, {textures[SpecularOut].view, textures[SpecularOut].layout},
{textures[DiffuseAlbedo].view, textures[DiffuseAlbedo].layout}, {textures[SpecularAlbedo].view, textures[SpecularAlbedo].layout},
{textures[Residual].view, textures[Residual].layout}, {textures[Composed].view, textures[Composed].layout},
{textures[DiffuseT].view, textures[DiffuseT].layout}, {textures[SpecularT].view, textures[SpecularT].layout}};
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin (compose)");
passes.record_compose(command, compose, rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f));
int copiedImage = Composed;
if (layout.outputBytes == 16) {
// Preserve the denoiser/composition's half arithmetic; widen only the completed output on the GPU.
VkImageBlit region{}; region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.srcOffsets[1] = region.dstOffsets[1] = {int32_t(rw), int32_t(rh), 1};
api.blitImage(command, textures[Composed].image, textures[Composed].layout,
textures[Widened].image, textures[Widened].layout, 1, &region, VK_FILTER_NEAREST);
passes.full_barrier(command);
copiedImage = Widened;
}
VkBufferImageCopy back{}; back.bufferOffset = layout.outputOffset; back.bufferRowLength = uint32_t(layout.outputPitch / layout.outputBytes);
back.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; back.imageExtent = {rw, rh, 1};
api.copyImageToBuffer(command, textures[copiedImage].image, textures[copiedImage].layout, packetBuffer, 1, &back);
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = vkFamily; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end (compose)");
} }
VkBufferImageCopy back{}; back.bufferOffset = layout.outputOffset; back.bufferRowLength = uint32_t(layout.outputPitch / layout.outputBytes);
back.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; back.imageExtent = {rw, rh, 1};
api.copyImageToBuffer(command, textures[copiedImage].image, textures[copiedImage].layout, packetBuffer, 1, &back);
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = vkFamily; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end (compose)");
// Diagnostic (D4R_FSR_RR_DUMP_DIR, first D4R_FSR_RR_DUMP_FRAMES frames, default 3): raw copies of the converted // Diagnostic (D4R_FSR_RR_DUMP_DIR, first D4R_FSR_RR_DUMP_FRAMES frames, default 3): raw copies of the converted
// linear depth and motion textures, to check the DLSS -> FFX guide conversion numerically. // linear depth and motion textures, to check the DLSS -> FFX guide conversion numerically.
stamp(3); stamp(3);
@@ -917,13 +1026,26 @@ FsrBackend::~FsrBackend() {
(void)impl.release(); (void)impl.release();
} else impl.reset(); } else impl.reset();
} }
namespace {
// AMD's denoiser has no disocclusion or subsurface inputs. Games pass them anyway (Cyberpunk 2077 sends
// both every frame), so they are dropped rather than failing the frame. The shim reuses the Subsurface
// slot for the diffuse hit distance (frame.diffuseHit); a real subsurface guide is dropped here.
Snapshot without_unused_guides(const Snapshot& frame) {
Snapshot used = frame;
used.resources[Snapshot::Disocclusion].Reset();
if (!used.diffuseHit) used.resources[Snapshot::Subsurface].Reset();
return used;
}
}
void FsrBackend::evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) { void FsrBackend::evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
std::lock_guard<std::mutex> lock(hostMutex); impl->evaluate(queue, frame, access); std::lock_guard<std::mutex> lock(hostMutex); impl->evaluate(queue, without_unused_guides(frame), access);
} }
void FsrBackend::set_super_sampling(SuperSampling stage, const std::array<unsigned, 4>& renderLimits) { void FsrBackend::set_super_sampling(SuperSampling stage, const std::array<unsigned, 4>& renderLimits) {
std::lock_guard<std::mutex> lock(hostMutex); impl->superSampling = std::move(stage); impl->srRenderLimits = renderLimits; std::lock_guard<std::mutex> lock(hostMutex); impl->superSampling = std::move(stage); impl->srRenderLimits = renderLimits;
} }
void FsrBackend::validate_frame(const Snapshot& frame) { std::lock_guard<std::mutex> lock(hostMutex); impl->validate_frame(frame); } void FsrBackend::validate_frame(const Snapshot& frame) {
std::lock_guard<std::mutex> lock(hostMutex); impl->validate_frame(without_unused_guides(frame));
}
void FsrBackend::drain() { void FsrBackend::drain() {
std::lock_guard<std::mutex> lock(hostMutex); std::lock_guard<std::mutex> lock(hostMutex);
if (impl->pending || impl->failed) throw std::runtime_error("FSR RR backend cannot retire failed GPU work"); if (impl->pending || impl->failed) throw std::runtime_error("FSR RR backend cannot retire failed GPU work");
+4 -1
View File
@@ -7,18 +7,21 @@
namespace d4r::win::rr { namespace d4r::win::rr {
// Where the experimental FSR Ray Regeneration backend finds its pieces. All paths are explicit: nothing is // Where the experimental FSR Ray Regeneration backend finds its pieces. All paths are explicit: nothing is
// searched for, and AMD's amdxc64.dll is never loaded. // searched for. The compatibility route uses d4r's amdxc64 shim; native D3D12 uses AMD's real driver.
struct FsrConfig { struct FsrConfig {
std::wstring vkd3dCore; // d4r's vkd3d-proton d3d12core.dll, renamed so it cannot collide with the system's std::wstring vkd3dCore; // d4r's vkd3d-proton d3d12core.dll, renamed so it cannot collide with the system's
std::wstring denoiserDll; // AMD's amd_fidelityfx_denoiser_dx12.dll (FidelityFX SDK 2.3), unmodified std::wstring denoiserDll; // AMD's amd_fidelityfx_denoiser_dx12.dll (FidelityFX SDK 2.3), unmodified
std::wstring amdxcShim; // d4r's amdxc64 shim (tools/windows/amdxc64_shim) std::wstring amdxcShim; // d4r's amdxc64 shim (tools/windows/amdxc64_shim)
std::wstring cacheDir; // vkd3d-proton's shader cache: the denoiser's shaders are compiled once std::wstring cacheDir; // vkd3d-proton's shader cache: the denoiser's shaders are compiled once
static FsrConfig from_environment(); static FsrConfig from_environment();
static bool use_native_device();
}; };
// FSR Ray Regeneration as the Ray Reconstruction backend on native Windows. The game's guides cross to a private // FSR Ray Regeneration as the Ray Reconstruction backend on native Windows. The game's guides cross to a private
// vkd3d-proton device (D3D12 over Vulkan, dxil-spirv FP8 emulation) through the same shared packet the native // vkd3d-proton device (D3D12 over Vulkan, dxil-spirv FP8 emulation) through the same shared packet the native
// DLSS backend uses; AMD's denoiser runs there. Evaluation is synchronous, as the native backend's is. // DLSS backend uses; AMD's denoiser runs there. Evaluation is synchronous, as the native backend's is.
// That is the RDNA3 compatibility route. RDNA4 instead records all three passes on the game's
// D3D12 device and queue, with no Vulkan device or cross-API packet.
// The denoiser works at render resolution. When the output is larger (or a Super Sampling stage is wanted at all), // The denoiser works at render resolution. When the output is larger (or a Super Sampling stage is wanted at all),
// the owner installs one with set_super_sampling(): the denoised colour is then copied into a texture on the game's // the owner installs one with set_super_sampling(): the denoised colour is then copied into a texture on the game's
// device and handed to that callback together with the frame, which must record/submit the upscale into the game's // device and handed to that callback together with the frame, which must record/submit the upscale into the game's
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
struct D4rFsrRrPrepareConstants
{
float clipToView[16];
float viewToPreviousView[16];
float renderSize[4];
float motion[4];
uint32_t colorDepthBase[4];
uint32_t motionBaseFlags[4];
float depthParams[4];
float detail[4];
};
static_assert(sizeof(D4rFsrRrPrepareConstants) == 224, "fsr_rr_prepare.comp push constants");
enum D4rFsrRrPrepareFlags : uint32_t
{
kFsrRrHardwareDepth = 1u,
kFsrRrRoughnessPacked = 2u,
kFsrRrSpecularHit = 4u,
kFsrRrSpecularHitInW = 8u,
kFsrRrDiffuseHit = 16u,
kFsrRrDiffuseHitInW = 32u,
kFsrRrNormalsUnorm = 64u,
kFsrRrBeforeParticles = 128u,
kFsrRrInvalidNormalPassthrough = 256u,
};
+160
View File
@@ -0,0 +1,160 @@
#pragma once
#include "d3d12_external.h"
#include "fsr_rr_constants.h"
#include "fsr_rr_shaders_hlsl.generated.h"
#include <d3dcompiler.h>
#include <utility>
namespace d4r::win::rr {
// All descriptors belong to one synchronous evaluation. The owner retires the
// submission before rewriting this heap or releasing any referenced resource.
struct NativeFsrPasses {
struct Guide {
ComPtr<ID3D12Resource> resource;
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
};
struct Pipeline {
ComPtr<ID3D12RootSignature> root;
ComPtr<ID3D12PipelineState> state;
UINT constants = 0, srvs = 0, uavs = 0, offset = 0;
} prepare, compose, widen;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12DescriptorHeap> heap;
UINT stride = 0;
D3D12_CPU_DESCRIPTOR_HANDLE cpu{};
D3D12_GPU_DESCRIPTOR_HANDLE gpu{};
void pipeline(Pipeline& p, const char* source, UINT constants, UINT srvs, UINT uavs, UINT offset) {
p.constants = constants; p.srvs = srvs; p.uavs = uavs; p.offset = offset;
// Load the OS compiler by absolute system search, never from the game directory.
static HMODULE compiler = LoadLibraryExW(L"d3dcompiler_47.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
auto compile = compiler ? reinterpret_cast<decltype(&D3DCompile)>(GetProcAddress(compiler, "D3DCompile")) : nullptr;
if (!compile) throw std::runtime_error("Native FSR RR requires Windows d3dcompiler_47.dll");
ComPtr<ID3DBlob> code, errors;
HRESULT result = compile(source, std::strlen(source), "d4r-fsr-rr", nullptr, nullptr, "main", "cs_5_1",
D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_IEEE_STRICTNESS, 0, &code, &errors);
if (FAILED(result)) throw std::runtime_error(std::string("FSR RR native shader: ") +
(errors ? static_cast<const char*>(errors->GetBufferPointer()) : "compilation failed"));
D3D12_DESCRIPTOR_RANGE ranges[2]{};
D3D12_ROOT_PARAMETER params[3]{};
params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
params[0].Constants = {0, 0, constants};
UINT count = 1, range = 0;
for (auto binding : {std::pair<D3D12_DESCRIPTOR_RANGE_TYPE, UINT>{D3D12_DESCRIPTOR_RANGE_TYPE_SRV, srvs},
{D3D12_DESCRIPTOR_RANGE_TYPE_UAV, uavs}}) {
if (!binding.second) continue;
ranges[range] = {binding.first, binding.second, 0, 0, D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND};
params[count].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
params[count].DescriptorTable = {1, &ranges[range]};
++count; ++range;
}
D3D12_ROOT_SIGNATURE_DESC desc{}; desc.NumParameters = count; desc.pParameters = params;
ComPtr<ID3DBlob> serialized;
dx(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &serialized, &errors), "FSR RR root serialization");
dx(device->CreateRootSignature(0, serialized->GetBufferPointer(), serialized->GetBufferSize(), IID_PPV_ARGS(&p.root)), "FSR RR root signature");
D3D12_COMPUTE_PIPELINE_STATE_DESC state{}; state.pRootSignature = p.root.Get();
state.CS = {code->GetBufferPointer(), code->GetBufferSize()};
dx(device->CreateComputePipelineState(&state, IID_PPV_ARGS(&p.state)), "FSR RR native pipeline");
}
void initialize(ID3D12Device* owner) {
device = owner;
for (auto format : {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT}) {
D3D12_FEATURE_DATA_FORMAT_SUPPORT support{}; support.Format = format;
dx(device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support)), "FSR RR native format support");
const UINT wanted = D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD | D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE;
if ((support.Support2 & wanted) != wanted) throw std::runtime_error("FSR RR native typed UAV formats are unsupported");
}
D3D12_DESCRIPTOR_HEAP_DESC desc{}; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
desc.NumDescriptors = 28; desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
dx(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&heap)), "FSR RR native descriptor heap");
stride = device->GetDescriptorHandleIncrementSize(desc.Type);
#if defined(__MINGW32__)
heap->GetCPUDescriptorHandleForHeapStart(&cpu); heap->GetGPUDescriptorHandleForHeapStart(&gpu);
#else
cpu = heap->GetCPUDescriptorHandleForHeapStart(); gpu = heap->GetGPUDescriptorHandleForHeapStart();
#endif
pipeline(prepare, d4r_hlsl_fsr_rr_prepare, sizeof(D4rFsrRrPrepareConstants) / 4, 10, 8, 0);
pipeline(compose, d4r_hlsl_fsr_rr_compose, 4, 0, 8, 18);
const char* widening = R"HLSL(
Texture2D<float4> source : register(t0);
RWTexture2D<float4> destination : register(u0);
cbuffer Constants : register(b0) { uint2 extent; }
[numthreads(8,8,1)] void main(uint3 p : SV_DispatchThreadID) {
if (all(p.xy < extent)) destination[p.xy] = source.Load(int3(p.xy, 0));
})HLSL";
pipeline(widen, widening, 2, 1, 1, 26);
}
static DXGI_FORMAT guide_format(DXGI_FORMAT format, bool depth) {
if (depth) switch (format) {
case DXGI_FORMAT_R32G8X24_TYPELESS: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_D32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R16_TYPELESS: case DXGI_FORMAT_D16_UNORM: return DXGI_FORMAT_R16_UNORM;
default: return format;
}
switch (format) {
case DXGI_FORMAT_R8_TYPELESS: return DXGI_FORMAT_R8_UNORM;
case DXGI_FORMAT_R8G8_TYPELESS: return DXGI_FORMAT_R8G8_UNORM;
case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_B8G8R8A8_TYPELESS: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM;
case DXGI_FORMAT_R16_TYPELESS: return DXGI_FORMAT_R16_FLOAT;
case DXGI_FORMAT_R16G16_TYPELESS: return DXGI_FORMAT_R16G16_FLOAT;
case DXGI_FORMAT_R16G16B16A16_TYPELESS: return DXGI_FORMAT_R16G16B16A16_FLOAT;
case DXGI_FORMAT_R32_TYPELESS: return DXGI_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R32G32_TYPELESS: return DXGI_FORMAT_R32G32_FLOAT;
case DXGI_FORMAT_R32G32B32A32_TYPELESS: return DXGI_FORMAT_R32G32B32A32_FLOAT;
default: return format;
}
}
void copy_guide(ID3D12GraphicsCommandList* list, ID3D12Resource* source, const ResourceAccess& access,
bool depth, UINT width, UINT height, UINT maxWidth, UINT maxHeight, UINT x, UINT y, Guide& guide) {
// Copy into typed, shader-readable textures on the same device. This also
// supports game depth resources created with DENY_SHADER_RESOURCE.
const auto desc = resource_desc(source);
const auto format = guide_format(desc.Format, depth);
if (!guide.resource || guide.format != format) {
guide.resource.Reset(); guide.format = format;
D3D12_RESOURCE_DESC target{}; target.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
target.Width = maxWidth; target.Height = maxHeight; target.DepthOrArraySize = target.MipLevels = 1;
target.Format = format == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS ? DXGI_FORMAT_R32G8X24_TYPELESS : format;
target.SampleDesc.Count = 1;
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &target,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&guide.resource)), "FSR RR native guide texture");
}
transition(list, guide.resource.Get(), D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
texture_copy_barrier(list, source, access, false, false);
D3D12_TEXTURE_COPY_LOCATION src{}, dst{};
src.pResource = source; src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.pResource = guide.resource.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_BOX box{x, y, 0, x + width, y + height, 1};
list->CopyTextureRegion(&dst, 0, 0, 0, &src, (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) ? nullptr : &box);
texture_copy_barrier(list, source, access, false, true);
transition(list, guide.resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
}
void record(ID3D12GraphicsCommandList* list, const Pipeline& p, const void* constants,
ID3D12Resource* const* resources, const DXGI_FORMAT* formats, UINT width, UINT height) {
for (UINT i = 0; i < p.srvs + p.uavs; ++i) {
auto handle = cpu; handle.ptr += SIZE_T(p.offset + i) * stride;
if (i < p.srvs) {
D3D12_SHADER_RESOURCE_VIEW_DESC view{}; view.Format = formats[i];
view.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
view.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; view.Texture2D.MipLevels = 1;
device->CreateShaderResourceView(resources[i], &view, handle);
} else {
D3D12_UNORDERED_ACCESS_VIEW_DESC view{}; view.Format = formats[i]; view.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
device->CreateUnorderedAccessView(resources[i], nullptr, &view, handle);
}
}
ID3D12DescriptorHeap* heaps[] = {heap.Get()}; list->SetDescriptorHeaps(1, heaps);
list->SetComputeRootSignature(p.root.Get()); list->SetPipelineState(p.state.Get());
list->SetComputeRoot32BitConstants(0, p.constants, constants, 0);
UINT table = 1; auto handle = gpu; handle.ptr += UINT64(p.offset) * stride;
if (p.srvs) { list->SetComputeRootDescriptorTable(table++, handle); handle.ptr += UINT64(p.srvs) * stride; }
list->SetComputeRootDescriptorTable(table, handle);
list->Dispatch((width + 7) / 8, (height + 7) / 8, 1);
D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
list->ResourceBarrier(1, &barrier);
}
};
}
+195
View File
@@ -0,0 +1,195 @@
// Exercises native D3D12 guide conversion, composition and exact half widening.
// AMD's denoiser is deliberately absent: this tests our adapter on RDNA3 or WARP.
#include "fsr_rr_native_passes.h"
#include "fsr_rr_route.h"
#include "binary16.h"
#include <array>
#include <vector>
#include <cmath>
#include <cstdio>
using namespace d4r::win;
using namespace d4r::win::rr;
namespace diag = d4r::diag;
int main(int argc, char** argv) try {
if (native_fsr_route("gfx1101") || !native_fsr_route("gfx1200") || !native_fsr_route("gfx1201") ||
native_fsr_route("gfx1201", "vkd3d") || !native_fsr_route("gfx1101", "native"))
throw std::runtime_error("FSR route selection failed");
bool rejected = false;
try { native_fsr_route("gfx1201", "invalid"); } catch (...) { rejected = true; }
if (!rejected) throw std::runtime_error("Invalid FSR route accepted");
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "Factory");
ComPtr<IDXGIAdapter1> adapter;
auto option = [&](const char* name) { for (int i = 1; i < argc; ++i) if (std::string(argv[i]) == name) return true; return false; };
const bool warp = option("--warp"), sky = option("--sky"), depthStencil = option("--depth-stencil");
const bool hardwareDepth = option("--hardware-depth") || depthStencil, subrect = option("--subrect");
if (depthStencil && subrect) throw std::runtime_error("Use a full depth-stencil rectangle");
if (warp) dx(factory->EnumWarpAdapter(IID_PPV_ARGS(&adapter)), "WARP");
else {
for (UINT i = 0;; ++i) {
ComPtr<IDXGIAdapter1> candidate;
if (factory->EnumAdapters1(i, &candidate) == DXGI_ERROR_NOT_FOUND) break;
DXGI_ADAPTER_DESC1 desc{}; candidate->GetDesc1(&desc);
if (!(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { adapter = candidate; break; }
}
}
if (!adapter) throw std::runtime_error("No test adapter");
ComPtr<ID3D12Device> device; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device)), "Device");
NativeFsrPasses passes; passes.initialize(device.Get());
ComPtr<ID3D12CommandQueue> queue; D3D12_COMMAND_QUEUE_DESC qd{};
dx(device->CreateCommandQueue(&qd, IID_PPV_ARGS(&queue)), "Queue");
ComPtr<ID3D12CommandAllocator> allocator;
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "Allocator");
ComPtr<ID3D12GraphicsCommandList> list;
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "List");
constexpr UINT width = 65, height = 67;
auto texture = [&](DXGI_FORMAT format, D3D12_RESOURCE_STATES state, bool uav, UINT tw = 65, UINT th = 67) {
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = tw; desc.Height = th; desc.DepthOrArraySize = desc.MipLevels = 1;
desc.Format = format; desc.SampleDesc.Count = 1;
if (uav) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT)
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
ComPtr<ID3D12Resource> result;
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr, IID_PPV_ARGS(&result)), "Texture");
return result;
};
auto desc_of = [](ID3D12Resource* resource) {
D3D12_RESOURCE_DESC desc{};
#if defined(__MINGW32__)
resource->GetDesc(&desc);
#else
desc = resource->GetDesc();
#endif
return desc;
};
auto location = [](ID3D12Resource* resource) {
D3D12_TEXTURE_COPY_LOCATION result{}; result.pResource = resource;
result.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; return result;
};
std::array<ComPtr<ID3D12Resource>, 10> inputs;
std::vector<ComPtr<ID3D12Resource>> uploads;
float values[10][4] = {{2,1,.5f,1}, {2,0,0,0}, {.25f,-.5f,0,0}, {0,0,1,.75f}, {.5f,0,0,0},
{.5f,.5f,.5f,0}, {.25f,.25f,.25f,0}, {3,0,0,0}, {5,0,0,0}, {1,.5f,.25f,1}};
if (sky) values[3][2] = 0;
if (hardwareDepth) values[1][0] = .25f;
for (size_t i = 0; i < inputs.size(); ++i) {
const bool depth = depthStencil && i == 1;
inputs[i] = texture(depth ? DXGI_FORMAT_D32_FLOAT_S8X24_UINT : DXGI_FORMAT_R32G32B32A32_FLOAT,
D3D12_RESOURCE_STATE_COPY_DEST, false, width + (subrect ? 6 : 0), height + (subrect ? 8 : 0));
auto desc = desc_of(inputs[i].Get());
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; UINT64 bytes = 0;
device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, nullptr, nullptr, &bytes);
auto upload = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
void* mapped = nullptr; D3D12_RANGE empty{}; dx(upload->Map(0, &empty, &mapped), "Upload map");
const float padding[4] = {-10,-10,-10,-10};
const UINT bytesPerTexel = depth ? 4 : 16;
for (UINT y = 0; y < desc.Height; ++y) for (UINT x = 0; x < desc.Width; ++x) {
const bool inside = !subrect || (x >= 3 && x < width + 3 && y >= 4 && y < height + 4);
std::memcpy(static_cast<char*>(mapped) + y * footprint.Footprint.RowPitch + x * bytesPerTexel,
inside ? values[i] : padding, bytesPerTexel);
}
upload->Unmap(0, nullptr);
auto dst = location(inputs[i].Get()); D3D12_TEXTURE_COPY_LOCATION src{};
src.pResource = upload.Get(); src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint = footprint;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
transition(list.Get(), inputs[i].Get(), D3D12_RESOURCE_STATE_COPY_DEST,
depth ? D3D12_RESOURCE_STATE_DEPTH_READ : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
uploads.push_back(upload);
}
std::array<ComPtr<ID3D12Resource>, 8> prepared;
std::array<NativeFsrPasses::Guide, 10> guides;
ID3D12Resource* resources[18]{}; DXGI_FORMAT formats[18]{};
for (size_t i = 0; i < 10; ++i) {
const ResourceAccess access(depthStencil && i == 1 ? D3D12_RESOURCE_STATE_DEPTH_READ : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
passes.copy_guide(list.Get(), inputs[i].Get(), access, i == 1, width, height, width, height,
subrect ? 3 : 0, subrect ? 4 : 0, guides[i]);
resources[i] = guides[i].resource.Get(); formats[i] = guides[i].format;
}
for (size_t i = 0; i < 8; ++i) {
formats[10+i] = i == 0 ? DXGI_FORMAT_R32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT;
prepared[i] = texture(formats[10+i], D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true); resources[10+i] = prepared[i].Get();
}
D4rFsrRrPrepareConstants constants{};
for (int i : {0,5,10,15}) constants.clipToView[i] = constants.viewToPreviousView[i] = 1;
constants.viewToPreviousView[14] = .5f;
constants.renderSize[0] = width; constants.renderSize[1] = height;
constants.renderSize[2] = 1.0f/width; constants.renderSize[3] = 1.0f/height;
constants.motion[0] = 1.0f/width; constants.motion[1] = 1.0f/height;
constants.motion[2] = constants.motion[3] = 1;
constants.motionBaseFlags[2] = kFsrRrSpecularHit | kFsrRrDiffuseHit | kFsrRrBeforeParticles | kFsrRrInvalidNormalPassthrough;
if (hardwareDepth) constants.motionBaseFlags[2] |= kFsrRrHardwareDepth;
constants.depthParams[0] = constants.depthParams[1] = 1; constants.depthParams[2] = 1e6f;
constants.detail[1] = .1f;
passes.record(list.Get(), passes.prepare, &constants, resources, formats, width, height);
auto composed = texture(DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true);
ID3D12Resource* composition[] = {prepared[5].Get(), prepared[6].Get(), prepared[3].Get(), prepared[4].Get(),
prepared[7].Get(), composed.Get(), prepared[5].Get(), prepared[6].Get()};
DXGI_FORMAT halfFormats[8]; for (auto& format : halfFormats) format = DXGI_FORMAT_R16G16B16A16_FLOAT;
struct { UINT width, height; float halo; UINT unused; } composeConstants{width, height, 0, 0};
passes.record(list.Get(), passes.compose, &composeConstants, composition, halfFormats, width, height);
transition(list.Get(), composed.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
auto widened = texture(DXGI_FORMAT_R32G32B32A32_FLOAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true);
ID3D12Resource* widening[] = {composed.Get(), widened.Get()};
DXGI_FORMAT wideFormats[] = {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT};
const UINT extent[] = {width, height};
passes.record(list.Get(), passes.widen, extent, widening, wideFormats, width, height);
struct Readback { ComPtr<ID3D12Resource> resource; D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; };
std::vector<Readback> reads;
for (size_t i = 0; i < 10; ++i) {
auto* source = i < 8 ? prepared[i].Get() : i == 8 ? composed.Get() : widened.Get();
transition(list.Get(), source, i == 8 ? D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE : D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto desc = desc_of(source); Readback r{}; UINT64 bytes = 0;
device->GetCopyableFootprints(&desc, 0, 1, 0, &r.footprint, nullptr, nullptr, &bytes);
r.resource = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
auto src = location(source); D3D12_TEXTURE_COPY_LOCATION dst{};
dst.pResource = r.resource.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; dst.PlacedFootprint = r.footprint;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); reads.push_back(r);
}
dx(list->Close(), "Close"); ID3D12CommandList* lists[] = {list.Get()}; queue->ExecuteCommandLists(1, lists);
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)), "Fence");
Handle event; event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
dx(queue->Signal(fence.Get(), 1), "Signal"); dx(fence->SetEventOnCompletion(1, event.value), "Completion");
if (WaitForSingleObject(event.value, 30000) != WAIT_OBJECT_0 || fence->GetCompletedValue() == UINT64_MAX)
throw std::runtime_error("GPU test failed to retire");
double expected[10][4] = {{2}, {.25/width,-.5/height,.5,0}, {.5,.5,.5,0}, {.5,.5,.5,0},
{.25,.25,.25,0}, {4.0/3,2.0/3,1.0/3,5}, {4.0/3,2.0/3,1.0/3,3}, {1,.5,.25,0}, {2,1,.5,1}, {2,1,.5,1}};
if (hardwareDepth) expected[0][0] = .25;
if (sky) {
for (int i = 0; i < 8; ++i) for (double& value : expected[i]) value = 0;
expected[0][0] = 1e7;
expected[2][0] = expected[2][1] = .5; expected[2][2] = 1;
expected[7][0] = 2; expected[7][1] = 1; expected[7][2] = .5;
}
// D3D's typed float32-to-float16 store truncates toward zero. Compute the
// independent expected composition from the specified half signal values.
for (int c = 0; c < 3; ++c) {
const double signal = diag::binary16::half_value(diag::binary16::half_bits(expected[5][c]));
const double color = signal * (sky ? 0 : .75) + expected[7][c];
uint16_t bits = diag::binary16::half_bits(color);
if (diag::binary16::half_value(bits) > color) --bits;
expected[8][c] = expected[9][c] = diag::binary16::half_value(bits);
}
std::vector<std::vector<uint16_t>> halfData(10);
for (size_t i = 0; i < reads.size(); ++i) {
void* mapped = nullptr; dx(reads[i].resource->Map(0, nullptr, &mapped), "Readback map");
const bool wide = i == 0 || i == 9; const UINT channels = i == 0 ? 1 : 4;
for (UINT y = 0; y < height; ++y) for (UINT x = 0; x < width; ++x) for (UINT c = 0; c < channels; ++c) {
const char* p = static_cast<const char*>(mapped) + y * reads[i].footprint.Footprint.RowPitch + (x * channels + c) * (wide ? 4 : 2);
float value; uint16_t half = 0;
if (wide) std::memcpy(&value, p, 4);
else { std::memcpy(&half, p, 2); value = float(diag::binary16::half_value(half)); }
const double wanted = wide ? expected[i][c] : diag::binary16::half_value(diag::binary16::half_bits(expected[i][c]));
if (!std::isfinite(value) || std::abs(double(value) - wanted) > 1e-6)
throw std::runtime_error("Native conversion mismatch plane=" + std::to_string(i) + " channel=" + std::to_string(c) + " value=" + std::to_string(value));
if (i == 8) halfData[8].push_back(half);
if (i == 9 && value != diag::binary16::half_value(halfData[8][(y * width + x) * 4 + c]))
throw std::runtime_error("Native output widening is not exact");
}
reads[i].resource->Unmap(0, nullptr);
}
std::printf("PASS FSR_NATIVE_CONVERSIONS adapter=%s sky=%d hardware_depth=%d depth_stencil=%d subrect=%d pixels=%u prepared_planes=8 composed=1 exact_widening=1 route_contract=1 denoiser_executed=0\n",
warp ? "WARP" : "hardware", sky, hardwareDepth, depthStencil, subrect, width*height);
return 0;
} catch (const std::exception& error) { std::fprintf(stderr, "FAIL FSR_NATIVE_CONVERSIONS %s\n", error.what()); return 1; }
+1 -25
View File
@@ -11,31 +11,7 @@
#include "fsr_rr/fsr_rr_shaders_spv.h" #include "fsr_rr/fsr_rr_shaders_spv.h"
struct D4rFsrRrPrepareConstants #include "fsr_rr_constants.h"
{
float clipToView[16];
float viewToPreviousView[16];
float renderSize[4];
float motion[4];
uint32_t colorDepthBase[4];
uint32_t motionBaseFlags[4];
float depthParams[4];
float detail[4];
};
static_assert(sizeof(D4rFsrRrPrepareConstants) == 224, "fsr_rr_prepare.comp push constants");
enum D4rFsrRrPrepareFlags : uint32_t
{
kFsrRrHardwareDepth = 1u,
kFsrRrRoughnessPacked = 2u,
kFsrRrSpecularHit = 4u,
kFsrRrSpecularHitInW = 8u,
kFsrRrDiffuseHit = 16u,
kFsrRrDiffuseHitInW = 32u,
kFsrRrNormalsUnorm = 64u,
kFsrRrBeforeParticles = 128u,
kFsrRrInvalidNormalPassthrough = 256u,
};
// One image a pass reads or writes: the view and the layout the image is in while the pass runs. // One image a pass reads or writes: the view and the layout the image is in while the pass runs.
struct D4rFsrRrImage struct D4rFsrRrImage
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <stdexcept>
#include <string_view>
namespace d4r::win::rr {
inline bool native_fsr_route(std::string_view architecture, const char* overrideMode = nullptr) {
const std::string_view mode = overrideMode ? overrideMode : "auto";
if (mode == "native") return true;
if (mode == "vkd3d") return false;
if (mode.empty() || mode == "auto") return architecture.rfind("gfx12", 0) == 0;
throw std::runtime_error("D4R_FSR_RR_DEVICE must be auto, native or vkd3d");
}
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
// Embedded at CMake configure time; native conversion never reads source files at runtime.
static const char d4r_hlsl_fsr_rr_prepare[] = R"D4RHLSL(@d4r_hlsl_fsr_rr_prepare@)D4RHLSL";
static const char d4r_hlsl_fsr_rr_compose[] = R"D4RHLSL(@d4r_hlsl_fsr_rr_compose@)D4RHLSL";
+105 -5
View File
@@ -12,6 +12,7 @@ import re
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
import time
import urllib.request import urllib.request
import uuid import uuid
@@ -47,6 +48,41 @@ def external(path, game):
raise RuntimeError('Cache and backups must stay outside game directory: ' + str(path)) raise RuntimeError('Cache and backups must stay outside game directory: ' + str(path))
def format_eta(seconds):
seconds = int(seconds)
hours, rest = divmod(seconds, 3600)
minutes, seconds = divmod(rest, 60)
if hours:
return '{}h {:02d}m {:02d}s'.format(hours, minutes, seconds)
if minutes:
return '{}m {:02d}s'.format(minutes, seconds)
return str(seconds) + 's'
def copy_with_progress(response, out, name, chunk=1 << 20):
"""Copy a download to `out`, rewriting one status line with percent, speed and ETA."""
total = int(response.headers.get('Content-Length') or 0)
done, start, last = 0, time.monotonic(), 0.0
while True:
block = response.read(chunk)
if not block:
break
out.write(block)
done += len(block)
now = time.monotonic()
if now - last >= 0.25 or (total and done >= total):
last = now
speed = done / max(now - start, 1e-6)
line = ' {} {:.1f} MiB'.format(name, done / 1048576)
if total:
line += ' / {:.1f} MiB ({:.0f}%)'.format(total / 1048576, 100 * done / total)
line += ' at {:.1f} MiB/s'.format(speed / 1048576)
if total and done < total:
line += ', ETA ' + format_eta((total - done) / speed)
print('\r' + line.ljust(79), end='', flush=True)
print(flush=True)
def verified_download(path, url, expected, offline=False): def verified_download(path, url, expected, offline=False):
if path.exists(): if path.exists():
if path.is_file() and sha(path) == expected: if path.is_file() and sha(path) == expected:
@@ -61,7 +97,7 @@ def verified_download(path, url, expected, offline=False):
try: try:
print('DOWNLOAD ' + path.name, flush=True) print('DOWNLOAD ' + path.name, flush=True)
with urllib.request.urlopen(url, timeout=60) as response, temporary.open('xb') as out: with urllib.request.urlopen(url, timeout=60) as response, temporary.open('xb') as out:
shutil.copyfileobj(response, out) copy_with_progress(response, out, path.name)
if sha(temporary) != expected: if sha(temporary) != expected:
raise RuntimeError('Download SHA256 mismatch: ' + path.name) raise RuntimeError('Download SHA256 mismatch: ' + path.name)
os.replace(temporary, path) os.replace(temporary, path)
@@ -70,6 +106,17 @@ def verified_download(path, url, expected, offline=False):
return path return path
def native_fsr_package(runtime):
"""Only explicit RDNA4-only packages can omit the compatibility payload."""
target = runtime / 'gpu-target.json'
architectures = set()
if target.is_file():
architectures.add(json.loads(target.read_text(encoding='utf-8-sig')).get('architecture'))
if (runtime / 'package.json').is_file():
architectures.update(json.loads((runtime / 'package.json').read_text(encoding='utf-8-sig')).get('targets', {}))
return bool(architectures) and architectures <= {'gfx1200', 'gfx1201'}
def ini_selects_fsr(runtime): def ini_selects_fsr(runtime):
"""True when d4r.ini sets [RayReconstruction] Backend = fsr (comments and case ignored).""" """True when d4r.ini sets [RayReconstruction] Backend = fsr (comments and case ignored)."""
section = '' section = ''
@@ -139,6 +186,48 @@ def enable_fsr(runtime, backup_root):
return backup return backup
def absolutize_library_paths(game, backup_root):
"""Make OptiScaler.ini's [Libraries] folders absolute for this game folder.
OptiScaler builds without patch 0006 reject a relative NvngxPath ("AllowExternalBackend requires an
absolute NvngxPath"), which leaves the AMD GPU DLSS-incapable and hides DLSS in its menu. Idempotent;
the previous file is kept outside the game.
"""
config = game / 'OptiScaler.ini'
if not config.is_file():
return None
original = config.read_bytes()
text = original.decode('utf-8-sig')
section, changed, lines = '', False, []
for line in text.splitlines(keepends=True):
stripped = line.strip()
if stripped.startswith('[') and stripped.endswith(']'):
section = stripped[1:-1].strip().lower()
elif section == 'libraries' and '=' in line and not stripped.startswith((';', '#')):
key, value = line.split('=', 1)
ending = line[len(line.rstrip('\r\n')):]
value = value.strip()
if (key.strip().lower() in ('nvngxpath', 'nvngxdlsspath', 'nvngxfeaturepath')
and value and not Path(value).is_absolute()):
line = key.rstrip() + '=' + str((game / value).resolve()) + ending
changed = True
lines.append(line)
if not changed:
return None
backup_root.mkdir(parents=True, exist_ok=True)
backup = backup_root / ('optiscaler-config-' + uuid.uuid4().hex + '.ini')
with backup.open('xb') as stream:
stream.write(original)
temporary = config.with_name(config.name + '.' + uuid.uuid4().hex + '.tmp')
with temporary.open('xb') as stream:
stream.write((b'\xef\xbb\xbf' if original.startswith(b'\xef\xbb\xbf') else b'') + ''.join(lines).encode('utf-8'))
if config.read_bytes() != original:
temporary.unlink()
raise RuntimeError('OptiScaler.ini changed during installation; preserved current configuration')
os.replace(temporary, config)
return backup
def collect(cache, offline, fsr=False): def collect(cache, offline, fsr=False):
assets = [] assets = []
for kind, (name, relative, url, expected) in PINS.items(): for kind, (name, relative, url, expected) in PINS.items():
@@ -293,6 +382,12 @@ def install_model(runtime, cache, backup, preset, enable):
python = tools / 'python/python.exe' python = tools / 'python/python.exe'
recipe = tools / 'recipe' recipe = tools / 'recipe'
output = recipe / 'build' / ('rr-' + preset.lower()) output = recipe / 'build' / ('rr-' + preset.lower())
# An interrupted build (window closed, Ctrl+C) leaves products without the builder's cache
# stamp, which --reuse rejects on every later run. Keep it aside and build afresh.
if output.exists() and not (output / 'windows-native-python-cache.json').is_file():
aside = output.with_name(output.name + '.incomplete-' + uuid.uuid4().hex)
output.rename(aside)
print('Previous RR model build was incomplete; kept at ' + str(aside) + ' and rebuilding.', flush=True)
# Each builder child has its own 600s deadline and kills a timed-out tree. # Each builder child has its own 600s deadline and kills a timed-out tree.
subprocess.run([str(python), str(recipe / 'tools/windows/build_rr_model.py'), subprocess.run([str(python), str(recipe / 'tools/windows/build_rr_model.py'),
'--dll', str(runtime / 'rr/nvngx_dlssd.dll'), '--preset', preset, '--dll', str(runtime / 'rr/nvngx_dlssd.dll'), '--preset', preset,
@@ -316,7 +411,7 @@ def main():
parser.add_argument('--enable-rr', action='store_true', help='select generated native RR model in d4r.ini') parser.add_argument('--enable-rr', action='store_true', help='select generated native RR model in d4r.ini')
parser.add_argument('--enable-fsr', action='store_true', help='install the FSR denoiser and select Backend = fsr in d4r.ini') parser.add_argument('--enable-fsr', action='store_true', help='install the FSR denoiser and select Backend = fsr in d4r.ini')
parser.add_argument('--install-fsr', action='store_true', parser.add_argument('--install-fsr', action='store_true',
help="also install AMD's pinned FSR Ray Regeneration denoiser (automatic when d4r.ini selects Backend = fsr)") help="require AMD's pinned FSR Ray Regeneration denoiser (installed automatically whenever the release has its d4r/fsr payload)")
args = parser.parse_args() args = parser.parse_args()
runtime = args.package_root / 'd4r' if (args.package_root / 'd4r/d4r.ini').is_file() else args.package_root runtime = args.package_root / 'd4r' if (args.package_root / 'd4r/d4r.ini').is_file() else args.package_root
if not (runtime / 'd4r.ini').is_file(): if not (runtime / 'd4r.ini').is_file():
@@ -332,13 +427,18 @@ def main():
raise RuntimeError('Release does not recognize pinned ' + kind) raise RuntimeError('Release does not recognize pinned ' + kind)
if args.enable_fsr and args.rr_preset: if args.enable_fsr and args.rr_preset:
parser.error('--enable-fsr and --rr-preset select different RR backends') parser.error('--enable-fsr and --rr-preset select different RR backends')
fsr = args.install_fsr or args.enable_fsr or ini_selects_fsr(runtime) # Install every backend's files up front so switching [RayReconstruction] Backend later needs no rerun.
if fsr and not all((runtime / 'fsr' / name).is_file() for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll')): # AMD's denoiser comes along whenever the release carries the FSR payload; requesting FSR without it fails.
fsr_payload = all((runtime / 'fsr' / name).is_file() for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll'))
native_fsr = native_fsr_package(runtime)
if (args.install_fsr or args.enable_fsr or ini_selects_fsr(runtime)) and not (fsr_payload or native_fsr):
parser.error("FSR Ray Regeneration needs this release's d4r/fsr payload (d4r_vkd3d_core.dll, amdxc64.dll); extract the complete ZIP") parser.error("FSR Ray Regeneration needs this release's d4r/fsr payload (d4r_vkd3d_core.dll, amdxc64.dll); extract the complete ZIP")
if not args.yes and input('Download pinned DLSS/RR 310.9.1 and NGX 617.14 (~945 MiB archive)? [Y/n] ').lower().startswith('n'): fsr = fsr_payload or native_fsr
if not args.yes and input('Download pinned DLSS/RR 310.9.1 and NGX 617.14 (~945 MiB archive)? [Y/n] ').strip().lower().startswith('n'):
return return
result = transaction(runtime, collect(args.cache_root, args.offline, fsr), args.backup_root) result = transaction(runtime, collect(args.cache_root, args.offline, fsr), args.backup_root)
print(json.dumps(result, indent=2)) print(json.dumps(result, indent=2))
absolutize_library_paths(game, args.backup_root)
if args.enable_fsr: if args.enable_fsr:
backup = enable_fsr(runtime, args.backup_root) backup = enable_fsr(runtime, args.backup_root)
print(json.dumps({'fsrBackendSelected': True, 'configBackup': str(backup) if backup else None}, indent=2)) print(json.dumps({'fsrBackendSelected': True, 'configBackup': str(backup) if backup else None}, indent=2))
+15 -2
View File
@@ -35,8 +35,21 @@ struct RuntimeInitialization {
RuntimeInitialization(const RuntimeInitialization&) = delete; RuntimeInitialization(const RuntimeInitialization&) = delete;
RuntimeInitialization& operator=(const RuntimeInitialization&) = delete; RuntimeInitialization& operator=(const RuntimeInitialization&) = delete;
std::shared_ptr<Runtime> create() const { std::shared_ptr<Runtime> create() const {
return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk, try {
hasCommon ? &common : nullptr, hasProject ? &project : nullptr); return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk,
hasCommon ? &common : nullptr, hasProject ? &project : nullptr);
} catch (const std::exception& error) {
if (hasProject) throw;
// NGX 617.14 rejects application-ID initialization on this path (Cyberpunk 2077:
// 0xBAD0000C, or SR unavailable with 0xBAD00004). Streamline's RR plugin reads
// capabilities right after that first Init, so a failure there hides RR for the
// session even though the game's later project-ID Init works. Retry with d4r's
// own project identity (the diagnostics' default) so the first Init succeeds.
std::fprintf(stderr, "D4R_NGX_IDENTITY_FALLBACK application=%llu: %s\n", application, error.what());
const ngx::ProjectIdentity fallback{"24480451-f00d-face-1304-0308dabad187", 0, "1.0"};
return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk,
hasCommon ? &common : nullptr, &fallback);
}
} }
}; };
} }
+6 -3
View File
@@ -691,10 +691,10 @@ private:
mark("ngx_all_stream_completion"); mark("ngx_all_stream_completion");
auto& output = planes_[3]; auto& output = planes_[3];
output.image->download_device(reinterpret_cast<uintptr_t>(output.canonical ? output.canonical->data : output.shared->mapped), output.image->download_device(reinterpret_cast<uintptr_t>(output.canonical ? output.canonical->data : output.shared->mapped),
output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch); output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch, true);
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM output completion");
mark("output_array_download"); mark("output_array_download");
if (std::getenv("D4R_VALIDATE_OUTPUT")) { if (std::getenv("D4R_VALIDATE_OUTPUT")) {
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM diagnostic output completion");
if (!rt_->pixels) rt_->pixels = std::make_unique<PixelProgram>(rt_->hip, pixel_module_path()); if (!rt_->pixels) rt_->pixels = std::make_unique<PixelProgram>(rt_->hip, pixel_module_path());
const auto counts = rt_->pixels->validate(output.canonical ? output.canonical->data : output.shared->mapped, const auto counts = rt_->pixels->validate(output.canonical ? output.canonical->data : output.shared->mapped,
output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height); output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height);
@@ -702,8 +702,11 @@ private:
if (counts[0] || counts[1]) throw std::runtime_error("DLSS output contains NaN/Inf"); if (counts[0] || counts[1]) throw std::runtime_error("DLSS output contains NaN/Inf");
} }
mark("output_diagnostic"); mark("output_diagnostic");
if (output.canonical) rt_->pixels->convert(true, output.canonical->data, output.canonical->pitch, output.shared->mapped, if (output.canonical) rt_->pixels->convert_deferred(true, output.canonical->data, output.canonical->pitch, output.shared->mapped,
output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height, output.spec.storage, 3); output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height, output.spec.storage, 3);
// One completion covers the array download and optional format encode.
// NGX's nonblocking streams were completed before this ordered batch.
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM output batch completion");
if (ticket) rt_->timeline->signal_ready(ticket->output); else rt_->timeline->signal_output(queue); if (ticket) rt_->timeline->signal_ready(ticket->output); else rt_->timeline->signal_output(queue);
mark("output_conversion_fence_signal"); mark("output_conversion_fence_signal");
const double frameMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count(); const double frameMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count();
+10 -5
View File
@@ -19,8 +19,12 @@ inline PixelSpec pixel_spec(unsigned plane, DXGI_FORMAT format) {
case DXGI_FORMAT_R32G32B32A32_FLOAT: return {rgba32f, 16, false}; case DXGI_FORMAT_R32G32B32A32_FLOAT: return {rgba32f, 16, false};
case DXGI_FORMAT_R11G11B10_FLOAT: return {r11g11b10f, 4, false}; case DXGI_FORMAT_R11G11B10_FLOAT: return {r11g11b10f, 4, false};
case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_TYPELESS: return {rgb10a2, 4, false}; case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_TYPELESS: return {rgb10a2, 4, false};
case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: return {rgba8, 4, false}; // sRGB views are read as their stored UNORM values, as the Linux shim does.
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: return {bgra8, 4, false}; case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
return {rgba8, 4, false};
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
return {bgra8, 4, false};
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: return {rgb9e5, 4, false};
case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R16G16_TYPELESS: return {rg16f, 4, plane == motion}; case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R16G16_TYPELESS: return {rg16f, 4, plane == motion};
case DXGI_FORMAT_R32G32_FLOAT: case DXGI_FORMAT_R32G32_TYPELESS: return {rg32f, 8, false}; case DXGI_FORMAT_R32G32_FLOAT: case DXGI_FORMAT_R32G32_TYPELESS: return {rg32f, 8, false};
case DXGI_FORMAT_R16G16_SNORM: return {rg16snorm, 4, false}; case DXGI_FORMAT_R16G16_SNORM: return {rg16snorm, 4, false};
@@ -42,9 +46,10 @@ inline PixelSpec pixel_spec(unsigned plane, DXGI_FORMAT format) {
inline bool pixel_supported(unsigned plane, pixel::Storage storage) { inline bool pixel_supported(unsigned plane, pixel::Storage storage) {
using namespace pixel; using namespace pixel;
switch (plane) { switch (plane) {
case color: case output: return storage <= rgb10a2; // The Linux shim's supported_input/supported_output sets, plus the RG16 SNORM/UNORM motion formats.
case motion: return storage == rgba16f || storage == rgba32f || (storage >= rg16f && storage <= rg16unorm); case color: case output: return storage <= rgb10a2 || storage == rgb9e5;
case depth: return storage == r32f || storage == depth24 || storage == r16unorm; case motion: return storage == rgba16f || storage == rgba32f || storage == rgba8 || (storage >= rg16f && storage <= rg16unorm);
case depth: return storage == r32f || storage == depth24 || storage == r16unorm || storage == r16f;
case exposure: return storage == r32f || storage == r16f || storage == rgba16f || storage == rgba32f || case exposure: return storage == r32f || storage == r16f || storage == rgba16f || storage == rgba32f ||
storage == rgba8 || storage == bgra8; storage == rgba8 || storage == bgra8;
default: return false; default: return false;
+22
View File
@@ -85,6 +85,14 @@ __device__ inline void decode(const u8* p, Storage fmt, float* value) {
case r32f: value[0] = singles[0]; break; case r32f: value[0] = singles[0]; break;
case depth24: value[0] = float(*reinterpret_cast<const u32*>(p) & 0xffffffu) / 16777215.f; break; case depth24: value[0] = float(*reinterpret_cast<const u32*>(p) & 0xffffffu) / 16777215.f; break;
case r16unorm: value[0] = float(halves[0]) / 65535.f; break; case r16unorm: value[0] = float(halves[0]) / 65535.f; break;
case rgb9e5: {
// Microsoft spec: c = (p >> offset) & 0x1FF, E = p >> 27, v = c * 2^(E - 24);
// no implicit leading bit. 2^(E - 24) is a normal float for every E.
u32 packed = *reinterpret_cast<const u32*>(p);
const float scale = as_float(((packed >> 27) + 103u) << 23);
for (unsigned i = 0; i < 3; ++i) value[i] = float((packed >> (9 * i)) & 511u) * scale;
break;
}
default: break; // Host rejects formats which have no tested decoder. default: break; // Host rejects formats which have no tested decoder.
} }
} }
@@ -103,6 +111,20 @@ __device__ inline void encode(u8* p, Storage fmt, const float* value) {
case rgb10a2: case rgb10a2:
*reinterpret_cast<u32*>(p) = quantize(value[0], 1023) | (quantize(value[1], 1023) << 10) | *reinterpret_cast<u32*>(p) = quantize(value[0], 1023) | (quantize(value[1], 1023) << 10) |
(quantize(value[2], 1023) << 20) | (quantize(value[3], 3) << 30); break; (quantize(value[2], 1023) << 20) | (quantize(value[3], 3) << 30); break;
case rgb9e5: {
// Shared exponent from the largest channel (D3D11.3 spec 3.2.2): negative and
// NaN clamp to 0, the maximum is (511/512) * 2^16.
float c[3];
for (unsigned i = 0; i < 3; ++i) c[i] = value[i] > 0 ? (value[i] < 65408.f ? value[i] : 65408.f) : 0.f;
float largest = c[0] > c[1] ? c[0] : c[1]; largest = largest > c[2] ? largest : c[2];
int floorLog2 = int((as_uint(largest) >> 23) & 255u) - 127;
int shared = (floorLog2 < -16 ? -16 : floorLog2) + 16; // biased exponent, 0..31
float step = as_float(u32(shared - 24 + 127) << 23); // 2^(shared - 24)
if (u32(largest / step + .5f) == 512u) { ++shared; step *= 2.f; }
u32 packed = u32(shared) << 27;
for (unsigned i = 0; i < 3; ++i) packed |= (u32(c[i] / step + .5f) & 511u) << (9 * i);
*reinterpret_cast<u32*>(p) = packed; break;
}
default: break; // Only color/output formats are encoded in the runtime. default: break; // Only color/output formats are encoded in the runtime.
} }
} }
+2 -1
View File
@@ -6,7 +6,8 @@ namespace d4r::pixel {
enum Storage : unsigned { enum Storage : unsigned {
rgba16f, rgba32f, rgba8, bgra8, r11g11b10f, rgb10a2, rgba16f, rgba32f, rgba8, bgra8, r11g11b10f, rgb10a2,
rg16f, rg32f, rg16snorm, rg16unorm, r16f, r32f, rg16f, rg32f, rg16snorm, rg16unorm, r16f, r32f,
depth24, r16unorm, rgba8srgb, bgra8srgb depth24, r16unorm, rgba8srgb, bgra8srgb,
rgb9e5 // DXGI_FORMAT_R9G9B9E5_SHAREDEXP
}; };
enum Plane : unsigned { color, depth, motion, output, exposure }; enum Plane : unsigned { color, depth, motion, output, exposure };
} }
+4 -2
View File
@@ -273,8 +273,10 @@ struct Replay {
: layout(l), physical(physical), device(device) { : layout(l), physical(physical), device(device) {
const auto data = read(std::filesystem::path(directory) / "rr.txt"); const auto data = read(std::filesystem::path(directory) / "rr.txt");
const std::string manifest(data.begin(), data.end()); const std::string manifest(data.begin(), data.end());
if (manifest.rfind("D4RR0001", 0) != 0 || manifest.find("arithmetic gfx11") == std::string::npos) const bool gfx12 = std::string(D4R_TARGET_ARCH).rfind("gfx12", 0) == 0;
throw std::runtime_error("This canonical RR bridge gate requires a D/E gfx11 model"); const char* arithmetic = gfx12 ? "\narithmetic gfx12\n" : "\narithmetic gfx11\n";
if (manifest.rfind("D4RR0001", 0) != 0 || manifest.find(arithmetic) == std::string::npos)
throw std::runtime_error("RR bridge model arithmetic does not match the package GPU family");
for (size_t i = 0; i < images.size(); ++i) if (l.planes[i].enabled) configure(i, l.planes[i].format); 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); model = std::make_unique<d4r::RModel>(physical, device, directory);
engine = std::make_unique<d4r::REngine>(*model, l.rw, l.rh, l.ow, l.oh); engine = std::make_unique<d4r::REngine>(*model, l.rw, l.rh, l.ow, l.oh);
+63
View File
@@ -0,0 +1,63 @@
#pragma once
// RR guide formats shared by the native and FSR RR backends. Include after volk.h and rr_bridge_replay.h.
#include <string>
namespace d4r::win::rr {
// The Linux shim's RR guide formats (supported_input and aux_array_layout): every UNORM/SNORM/float
// layout samples verbatim, as a D3D12 shader reads it; no CPU conversion or reinterpretation is involved.
// Typeless resources use the float/UNORM view and sRGB is read as stored UNORM, like Linux.
inline rr_bridge::Plane guide_format(DXGI_FORMAT format, size_t plane, const char* backend) {
if (plane == Snapshot::Depth) {
switch (format) {
case DXGI_FORMAT_R32_FLOAT: case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_D32_FLOAT:
case DXGI_FORMAT_R32G8X24_TYPELESS: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
case DXGI_FORMAT_R16_FLOAT: return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
case DXGI_FORMAT_D16_UNORM: case DXGI_FORMAT_R16_UNORM: case DXGI_FORMAT_R16_TYPELESS: return {nullptr, VK_FORMAT_R16_UNORM, 2};
default: break;
}
} else {
switch (format) {
case DXGI_FORMAT_R8_UNORM: case DXGI_FORMAT_R8_TYPELESS: return {nullptr, VK_FORMAT_R8_UNORM, 1};
case DXGI_FORMAT_R8_SNORM: return {nullptr, VK_FORMAT_R8_SNORM, 1};
case DXGI_FORMAT_R8G8_UNORM: case DXGI_FORMAT_R8G8_TYPELESS: return {nullptr, VK_FORMAT_R8G8_UNORM, 2};
case DXGI_FORMAT_R8G8_SNORM: return {nullptr, VK_FORMAT_R8G8_SNORM, 2};
case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
return {nullptr, VK_FORMAT_R8G8B8A8_UNORM, 4};
case DXGI_FORMAT_R8G8B8A8_SNORM: return {nullptr, VK_FORMAT_R8G8B8A8_SNORM, 4};
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
return {nullptr, VK_FORMAT_B8G8R8A8_UNORM, 4};
case DXGI_FORMAT_R16_UNORM: return {nullptr, VK_FORMAT_R16_UNORM, 2};
case DXGI_FORMAT_R16_SNORM: return {nullptr, VK_FORMAT_R16_SNORM, 2};
case DXGI_FORMAT_R16_FLOAT: case DXGI_FORMAT_R16_TYPELESS: return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
case DXGI_FORMAT_R16G16_UNORM: return {nullptr, VK_FORMAT_R16G16_UNORM, 4};
case DXGI_FORMAT_R16G16_SNORM: return {nullptr, VK_FORMAT_R16G16_SNORM, 4};
case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R16G16_TYPELESS: return {nullptr, VK_FORMAT_R16G16_SFLOAT, 4};
case DXGI_FORMAT_R16G16B16A16_UNORM: return {nullptr, VK_FORMAT_R16G16B16A16_UNORM, 8};
case DXGI_FORMAT_R16G16B16A16_SNORM: return {nullptr, VK_FORMAT_R16G16B16A16_SNORM, 8};
case DXGI_FORMAT_R16G16B16A16_FLOAT: case DXGI_FORMAT_R16G16B16A16_TYPELESS: return {nullptr, VK_FORMAT_R16G16B16A16_SFLOAT, 8};
case DXGI_FORMAT_R32_FLOAT: case DXGI_FORMAT_R32_TYPELESS: return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
case DXGI_FORMAT_R32G32_FLOAT: case DXGI_FORMAT_R32G32_TYPELESS: return {nullptr, VK_FORMAT_R32G32_SFLOAT, 8};
case DXGI_FORMAT_R32G32B32A32_FLOAT: case DXGI_FORMAT_R32G32B32A32_TYPELESS: return {nullptr, VK_FORMAT_R32G32B32A32_SFLOAT, 16};
case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_TYPELESS: return {nullptr, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4};
// Same bit layouts: R11G11B10 red in bits 0-10; R9G9B9E5 red in bits 0-8, exponent 27-31.
case DXGI_FORMAT_R11G11B10_FLOAT: return {nullptr, VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4};
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: return {nullptr, VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 4};
default: break;
}
}
// Linux also accepts UINT/SINT guides by converting them to float on the CPU; this
// sampled-image route has no conversion pass, so those still fail here, by name.
throw std::runtime_error(std::string("Unsupported ") + backend + " sampled input format: plane " + std::to_string(plane) +
" DXGI format " + std::to_string(unsigned(format)));
}
// UNORM guides store [0, 1]; normals in them are remapped to [-1, 1] before denoising.
inline bool unorm_guide(VkFormat format) {
switch (format) {
case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_R16G16B16A16_UNORM: case VK_FORMAT_R8G8_UNORM: case VK_FORMAT_R16G16_UNORM:
return true;
default: return false;
}
}
}
+7 -27
View File
@@ -2,6 +2,7 @@
#include "rr_native_backend.h" #include "rr_native_backend.h"
#include "rr_native_packet.h" #include "rr_native_packet.h"
#include "rr_bridge_replay.h" #include "rr_bridge_replay.h"
#include "rr_input_formats.h"
#include "d3d12_command_hooks.h" #include "d3d12_command_hooks.h"
#include <mutex> #include <mutex>
@@ -13,32 +14,7 @@ std::mutex dispatchMutex;
using Packet = rr_bridge::NativePacket; using Packet = rr_bridge::NativePacket;
using Device = rr_bridge::NativeDevice; using Device = rr_bridge::NativeDevice;
size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; } size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; }
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { return guide_format(format, plane, "native RR"); }
// 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) { DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) {
const auto desc = resource_desc(texture); const auto desc = resource_desc(texture);
if (plane != Snapshot::Depth) return desc.Format; if (plane != Snapshot::Depth) return desc.Format;
@@ -111,7 +87,11 @@ struct Backend::Impl {
replay = std::make_unique<rr_bridge::Replay>(layout, native->physical, native->device, directory); replay = std::make_unique<rr_bridge::Replay>(layout, native->physical, native->device, directory);
const unsigned indices[] = {3, 2, 1, 4, 5, 0}; const unsigned indices[] = {3, 2, 1, 4, 5, 0};
const auto requested = creation.presets[indices[creation.quality]]; 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"); // The preset is a hint (Cyberpunk 2077 asks for one per quality mode). Serve the locally
// built model rather than failing RR; the log names both so the user can build that preset.
if (requested && requested != replay->model->preset())
std::fprintf(stderr, "d4r: RR preset %u requested; serving the configured preset %u model (build the requested one with install.bat to match)\n",
unsigned(requested), unsigned(replay->model->preset()));
shared = make_buffer(d3d.Get(), layout.bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON); 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"); dx(d3d->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&fence)), "RR shared fence");
Handle memoryHandle, fenceHandle; Handle memoryHandle, fenceHandle;
+17 -1
View File
@@ -47,13 +47,28 @@ struct NativeDevice {
for (uint32_t i = 0; i < countFamilies; ++i) for (uint32_t i = 0; i < countFamilies; ++i)
if (properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { family = i; break; } if (properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { family = i; break; }
if (family == countFamilies) throw std::runtime_error("No Vulkan compute queue"); if (family == countFamilies) throw std::runtime_error("No Vulkan compute queue");
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cm{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR}; const bool fp8 = inference && std::string(D4R_TARGET_ARCH).rfind("gfx12", 0) == 0;
if (fp8) {
uint32_t extensionCount = 0;
check(vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, nullptr), "RR extension count");
std::vector<VkExtensionProperties> available(extensionCount);
check(vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, available.data()), "RR extensions");
bool supported = false;
for (const auto& extension : available)
supported |= std::strcmp(extension.extensionName, VK_EXT_SHADER_FLOAT8_EXTENSION_NAME) == 0;
if (!supported) throw std::runtime_error("RDNA4 native RR requires VK_EXT_shader_float8");
}
VkPhysicalDeviceShaderFloat8FeaturesEXT f8{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT8_FEATURES_EXT};
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cm{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR,
fp8 ? &f8 : nullptr};
VkPhysicalDeviceVulkan13Features v13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, &cm}; VkPhysicalDeviceVulkan13Features v13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, &cm};
VkPhysicalDeviceVulkan12Features v12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, &v13}; VkPhysicalDeviceVulkan12Features v12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, &v13};
VkPhysicalDeviceVulkan11Features v11{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &v12}; VkPhysicalDeviceVulkan11Features v11{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &v12};
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &v11}; VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &v11};
vkGetPhysicalDeviceFeatures2(physical, &features); vkGetPhysicalDeviceFeatures2(physical, &features);
if (!v12.timelineSemaphore) throw std::runtime_error("Timeline semaphore unsupported"); if (!v12.timelineSemaphore) throw std::runtime_error("Timeline semaphore unsupported");
if (fp8 && (!f8.shaderFloat8 || !f8.shaderFloat8CooperativeMatrix))
throw std::runtime_error("RDNA4 native RR requires FP8 cooperative matrices");
if (inference && (!cm.cooperativeMatrix || !v12.shaderFloat16 || !v11.storageBuffer16BitAccess || !v13.subgroupSizeControl)) if (inference && (!cm.cooperativeMatrix || !v12.shaderFloat16 || !v11.storageBuffer16BitAccess || !v13.subgroupSizeControl))
throw std::runtime_error("Missing RR cooperative-matrix/fp16/subgroup feature"); throw std::runtime_error("Missing RR cooperative-matrix/fp16/subgroup feature");
if (!inference) v13.pNext = nullptr; if (!inference) v13.pNext = nullptr;
@@ -62,6 +77,7 @@ struct NativeDevice {
qi.queueFamilyIndex = family; qi.queueCount = 1; qi.pQueuePriorities = &priority; qi.queueFamilyIndex = family; qi.queueCount = 1; qi.pQueuePriorities = &priority;
std::vector<const char*> extensions{"VK_KHR_external_memory_win32", "VK_KHR_external_semaphore_win32"}; 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); if (inference) extensions.push_back(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME);
if (fp8) extensions.push_back(VK_EXT_SHADER_FLOAT8_EXTENSION_NAME);
VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &features}; VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &features};
di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi; di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi;
di.enabledExtensionCount = uint32_t(extensions.size()); di.ppEnabledExtensionNames = extensions.data(); di.enabledExtensionCount = uint32_t(extensions.size()); di.ppEnabledExtensionNames = extensions.data();
+3
View File
@@ -54,6 +54,9 @@ struct Snapshot {
unsigned renderWidth = 0, renderHeight = 0; unsigned renderWidth = 0, renderHeight = 0;
unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0; unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0;
float exposureScale = 1; float exposureScale = 1;
// FSR RR only (set by the shim, as the Linux FSR backend reads them): the hit distance is the w of a
// ray-direction texture, and the Subsurface slot carries the diffuse hit distance instead.
bool specularHitInW = false, diffuseHit = false, diffuseHitInW = false;
static ID3D12Resource* resource(void* parameters, const char* name, const char* alias = nullptr) { static ID3D12Resource* resource(void* parameters, const char* name, const char* alias = nullptr) {
ID3D12Resource* value = nullptr; ID3D12Resource* value = nullptr;
(void)d4r_ngx_get_d3d12_resource(parameters, name, &value); (void)d4r_ngx_get_d3d12_resource(parameters, name, &value);
+40
View File
@@ -0,0 +1,40 @@
"""Compare bounded FP32 attention dots with sequential FP64 on the GPU."""
import argparse
import json
import os
from pathlib import Path
import struct
import subprocess
def main(args):
root = args.output_dir.resolve()
root.mkdir(parents=True, exist_ok=False)
count = 262144
(root / 'alloc-0.bin').write_bytes(bytes(count * 4))
(root / 'args.bin').write_bytes(struct.pack('<Q', 0x100000000))
(root / 'manifest.txt').write_text(
f'kernel exact_pv_probe\nlaunch {count // 32} 1 1 32 1 1 0\nargs 8\n'
f'alloc 0 0x100000000 {count * 4}\npointer 0 0 0\n')
with (root / 'stdout.log').open('wb') as out, (root / 'stderr.log').open('wb') as err:
completed = subprocess.run([
str(args.probe.resolve()), '--hip-root', str(args.hip_root.resolve()),
'--module', str(args.module.resolve()), '--fixture-dir', str(root),
'--output-dir', str(root / 'output'), '--iterations', '1'],
stdout=out, stderr=err, timeout=120, env=dict(os.environ, D4R_DIAG_DIR=str(root)))
if completed.returncode:
raise RuntimeError(f'GPU probe failed: {root}')
data = (root / 'output/alloc-0.bin').read_bytes()
mismatches = sum((word & 65535) != (word >> 16) for (word,) in struct.iter_unpack('<I', data))
summary = dict(passed=len(data) == count * 4 and mismatches == 0,
dots=count, differingHalfBits=mismatches)
(root / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
print(summary)
return 0 if summary['passed'] else 1
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
for name in ('probe', 'hip-root', 'module', 'output-dir'):
parser.add_argument('--' + name, type=Path, required=True)
raise SystemExit(main(parser.parse_args()))
+34 -2
View File
@@ -28,9 +28,13 @@ def main():
for name in ('probe', 'shim', 'runtime', 'core', 'dlss', 'fsr-dir', 'denoiser', 'fixture', 'output'): for name in ('probe', 'shim', 'runtime', 'core', 'dlss', 'fsr-dir', 'denoiser', 'fixture', 'output'):
parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve()) parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve())
parser.add_argument('--timeout', type=int, default=1500) parser.add_argument('--timeout', type=int, default=1500)
parser.add_argument('--device', choices=('auto', 'native', 'vkd3d'), default='auto', help='select the native D3D12 or compatibility route for this diagnostic')
parser.add_argument('--without-sr', action='store_true', help='omit SR/core dependencies and require the same-size FSR-only fallback') parser.add_argument('--without-sr', action='store_true', help='omit SR/core dependencies and require the same-size FSR-only fallback')
parser.add_argument('--cache-dir', type=lambda value: Path(value).resolve(), help='isolated cache for shader/compiler experiments') parser.add_argument('--cache-dir', type=lambda value: Path(value).resolve(), help='isolated cache for shader/compiler experiments')
parser.add_argument('--vkd3d-config', help='diagnostic per-process vkd3d configuration') parser.add_argument('--vkd3d-config', help='diagnostic per-process vkd3d configuration')
parser.add_argument('--vkd3d-debug', choices=('none', 'err', 'warn', 'fixme', 'info', 'trace'), help='diagnostic vkd3d logging level')
parser.add_argument('--shader-dump', type=lambda value: Path(value).resolve(), help='diagnostic original shader dump into a new directory')
parser.add_argument('--disable-builtin-shaders', action='store_true', help='diagnostic control: use the original translations in a locally optimized payload')
parser.add_argument('--fp16-rte', action='store_true', help='experimental dxil-spirv payload: retain layout workaround but omit global FP16 RTZ') parser.add_argument('--fp16-rte', action='store_true', help='experimental dxil-spirv payload: retain layout workaround but omit global FP16 RTZ')
parser.add_argument('--float-guides', action='store_true') parser.add_argument('--float-guides', action='store_true')
parser.add_argument('--switch-guides', action='store_true') parser.add_argument('--switch-guides', action='store_true')
@@ -69,7 +73,12 @@ def main():
fsr = staged / 'fsr' fsr = staged / 'fsr'
fsr.mkdir(exist_ok=True) fsr.mkdir(exist_ok=True)
for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll'): for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll'):
shutil.copy2(args.fsr_dir / name, fsr / name) source = args.fsr_dir / name
if args.device == 'native':
(fsr / name).unlink(missing_ok=True)
continue # prove that the direct route has no compatibility payload dependency
if source.is_file():
shutil.copy2(source, fsr / name)
shutil.copy2(args.denoiser, staged / 'amd_fidelityfx_denoiser_dx12.dll') shutil.copy2(args.denoiser, staged / 'amd_fidelityfx_denoiser_dx12.dll')
config = (repo / 'packaging/windows/d4r.ini').read_text() config = (repo / 'packaging/windows/d4r.ini').read_text()
if 'Backend = off' not in config: if 'Backend = off' not in config:
@@ -87,8 +96,17 @@ def main():
env = {key: value for key, value in os.environ.items() env = {key: value for key, value in os.environ.items()
if not key.startswith(('D4R_', 'VKD3D_', 'DXIL_')) and key not in ('HIP_PATH', 'ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')} if not key.startswith(('D4R_', 'VKD3D_', 'DXIL_')) and key not in ('HIP_PATH', 'ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
env['D4R_FSR_RR_LOG'] = '1' # per-frame stage timings in stderr; selection itself comes only from d4r.ini env['D4R_FSR_RR_LOG'] = '1' # per-frame stage timings in stderr; selection itself comes only from d4r.ini
if args.device != 'auto':
env['D4R_FSR_RR_DEVICE'] = args.device
if args.vkd3d_config: if args.vkd3d_config:
env['VKD3D_CONFIG'] = args.vkd3d_config env['VKD3D_CONFIG'] = args.vkd3d_config
if args.vkd3d_debug:
env['VKD3D_DEBUG'] = args.vkd3d_debug
if args.shader_dump:
args.shader_dump.mkdir(parents=True, exist_ok=False)
env['VKD3D_SHADER_DUMP_PATH'] = str(args.shader_dump)
if args.disable_builtin_shaders:
env['D4R_FSR_DISABLE_BUILTIN_SHADERS'] = '1'
if args.fp16_rte: if args.fp16_rte:
env['D4R_DXIL_F16_RTE'] = '1' env['D4R_DXIL_F16_RTE'] = '1'
if args.verify_output_widening: if args.verify_output_widening:
@@ -126,8 +144,9 @@ def main():
command.append('-'.join(modes)) command.append('-'.join(modes))
report = {'passed': False, 'gameAccessed': False, 'qualityReference': False, 'synthetic': True, report = {'passed': False, 'gameAccessed': False, 'qualityReference': False, 'synthetic': True,
'selectionMode': 'diagnostic environment without SR' if args.without_sr else 'player INI', 'selectionMode': 'diagnostic environment without SR' if args.without_sr else 'player INI',
'requestedDevice': args.device,
'artifacts': {str(path): sha(path) for path in (args.probe, args.shim, args.core, args.dlss, args.denoiser, 'artifacts': {str(path): sha(path) for path in (args.probe, args.shim, args.core, args.dlss, args.denoiser,
args.fsr_dir / 'd4r_vkd3d_core.dll', args.fsr_dir / 'amdxc64.dll')}} args.fsr_dir / 'd4r_vkd3d_core.dll', args.fsr_dir / 'amdxc64.dll') if path.is_file()}}
try: try:
with (args.output / 'stdout.log').open('wb') as out, (args.output / 'stderr.log').open('wb') as err: 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=args.timeout) result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=args.timeout)
@@ -184,11 +203,22 @@ def main():
raise RuntimeError('FSR output differs from the same-frame composed half image') raise RuntimeError('FSR output differs from the same-frame composed half image')
# player mode redirects the shim's stderr into its own log beside d4r_nvngx.dll # player mode redirects the shim's stderr into its own log beside d4r_nvngx.dll
err_text = (args.output / 'stderr.log').read_text(errors='replace') err_text = (args.output / 'stderr.log').read_text(errors='replace')
device_modes = re.findall(r'D4R_FSR_RR_DEVICE (native-d3d12|vkd3d) ', err_text)
if not device_modes:
raise RuntimeError('FSR RR did not identify its device route')
report['selectedDevice'] = device_modes[-1]
requested = {'native': 'native-d3d12', 'vkd3d': 'vkd3d'}.get(args.device)
if requested and report['selectedDevice'] != requested:
raise RuntimeError('FSR RR used a different device route than requested')
playerLog = staged / 'd4r_nvngx.log' playerLog = staged / 'd4r_nvngx.log'
if playerLog.exists(): if playerLog.exists():
err_text += playerLog.read_text(errors='replace') err_text += playerLog.read_text(errors='replace')
shutil.copy2(playerLog, args.output / 'd4r_nvngx.log') shutil.copy2(playerLog, args.output / 'd4r_nvngx.log')
report['superSamplingUsed'] = 'FSR_RR super sampling submitted' in err_text report['superSamplingUsed'] = 'FSR_RR super sampling submitted' in err_text
report['builtinShaderOptimizations'] = (sorted(set(re.findall(
r'using validated local optimization for shader ([0-9a-f]{16})', err_text)))
if args.vkd3d_debug in ('info', 'trace') else None)
report['builtinShadersDisabled'] = args.disable_builtin_shaders
if args.without_sr and report['superSamplingUsed']: if args.without_sr and report['superSamplingUsed']:
raise RuntimeError('SR ran in the FSR-only test') raise RuntimeError('SR ran in the FSR-only test')
report['superSamplingFrames'] = err_text.count('FSR_RR super sampling submitted') report['superSamplingFrames'] = err_text.count('FSR_RR super sampling submitted')
@@ -202,6 +232,8 @@ def main():
for i, name in enumerate(('prepareMedianMs', 'denoiseMedianMs', 'composeCopyMedianMs', 'totalMedianMs'))} for i, name in enumerate(('prepareMedianMs', 'denoiseMedianMs', 'composeCopyMedianMs', 'totalMedianMs'))}
report['gpuTiming']['warmupFrames'] = 4 report['gpuTiming']['warmupFrames'] = 4
report['gpuTiming']['measuredFrames'] = len(timings) - 4 report['gpuTiming']['measuredFrames'] = len(timings) - 4
report['gpuTiming']['tailDenoiseMedianMs'] = statistics.median(float(row[1]) for row in timings[-7:])
report['gpuTiming']['tailFrames'] = min(7, len(timings))
report.update(passed=True) report.update(passed=True)
print('PASS FSR_RR_NGX super_sampling=%d tail_rmse=%.4f input_rmse=%.4f ideal_nearest_rmse=%.4f' % ( print('PASS FSR_RR_NGX super_sampling=%d tail_rmse=%.4f input_rmse=%.4f ideal_nearest_rmse=%.4f' % (
report['superSamplingUsed'], report['quality']['tailOutputRmse'], report['quality']['tailInputRmse'], report['quality'].get('idealNearestRmse', float('nan')))) report['superSamplingUsed'], report['quality']['tailOutputRmse'], report['quality']['tailInputRmse'], report['quality'].get('idealNearestRmse', float('nan'))))
+4 -3
View File
@@ -11,6 +11,7 @@ import struct
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--gpu-arch', choices=('gfx1101', 'gfx1200', 'gfx1201'), default='gfx1101')
parser.add_argument('--contracts', action='store_true', help='also check capability readiness and negative contracts') 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('--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('--enhanced', action='store_true', help='exercise enhanced texture access and restoration')
@@ -55,13 +56,13 @@ def main():
if not key.startswith('D4R_') and key not in ('ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')} if not key.startswith('D4R_') and key not in ('ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
runtime = args.runtime runtime = args.runtime
native = runtime / 'native' native = runtime / 'native'
if (native / 'gfx1101').is_dir(): if (native / args.gpu_arch).is_dir():
native = native / 'gfx1101' native = native / args.gpu_arch
env.update(HIP_PATH=str(runtime / 'hip'), D4R_HIP_ROOT=str(runtime / 'hip'), D4R_NVCUDA_DLL=str(runtime / 'zluda/nvcuda.dll'), 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), 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_NGX_CORE=str(args.core), D4R_NVAPI_DLL=str(runtime / 'nvapi/nvapi64.dll'),
D4R_NVAPI_BACKEND=str(runtime / 'nvapi/backend/nvapi64.dll'), D4R_NVAPI_BACKEND=str(runtime / 'nvapi/backend/nvapi64.dll'),
D4R_FORMAT_MODULE=str(runtime / 'pixel_convert_gfx1101.hsaco'), D4R_FORMAT_MODULE=str(runtime / f'pixel_convert_{args.gpu_arch}.hsaco'),
D4R_ZLUDA_NATIVE_DIR=str(native), D4R_QUIET_API='1', D4R_ZLUDA_NATIVE_DIR=str(native), D4R_QUIET_API='1',
D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_F16_REFERENCE='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)) D4R_NGX_DATA_DIR=str(args.output / 'ngx-data'), D4R_RR_BACKEND='native', D4R_RR_MODEL_DIR=str(args.model))