scripts/check_redistributable.py refuses NVIDIA binaries (by name and
version resource), NGX SDK headers, PTX and code objects, PTX translations
(marked by ptx2glsl.py/condense.py) and the engine's model data in the
repository, and in each release variant (clean/installer/full) whatever
that variant does not contain. package_release.sh runs it before zipping;
scripts/git-hooks/pre-commit runs it locally.
Signed-off-by: woutermans <[email protected]>
A standalone Vulkan inference engine (engine/) for DLSS presets K and M that
runs from weights extracted from the user's own nvngx_dlss.dll, with d4r's
own input, network and reconstruction shaders or the native HIP network
layers, recorded into the game's command list by the shim. Opt-in
([Engine] Enabled / D4R_ENGINE=1); anything uncovered falls back to the
CUDA/ZLUDA path with the reason logged.
Byte-identical to NGX's CUDA backend under the phase-capable reference
(1706x960 -> 2560x1440, native HIP network, eleven frames, RX 9070 XT):
- K render- and display-resolution motion vectors, HDR and LDR
- K game-supplied exposure and automatic exposure with any pre-exposure
and exposure scale (one residual: effective exposure exactly 2 or 4)
- K and M with the DLSS indicator's inverted-axis parameters
- M with any exposure settings (NGX ignores them for M)
K packages need the current compile_k.py (marker D4RO0003).
Also: HIP shared buffers are pooled across feature recreation (ROCm leaks
destroyed imports), hipnet.bin must match the preset, M-only games do not
load the K model, install_d4r_runtime.sh stages k, k-ldr, m, m-ldr with
hip/, the activation table has a generator (gen_activation_m.py), and
package_release.sh can bundle the engine's shaders (never its weights).
Signed-off-by: woutermans <[email protected]>
Implement NGX's Vulkan API over the existing CUDA backend. Compute shaders
pack the game's sampled images into buffers shared with CUDA (device-local
memory exported through the Wine bridge), and a device event in the game's
command buffer holds its queue until the result is written back, keeping
frame age at zero. DLSS reads inputs through pitch-linear textures and its
native output kernel writes the shared buffer directly; input and
completion waits are adaptive.
Make it work across engines: shared buffers are concurrent over every
compute-capable queue family (Indiana Jones records DLSS on async compute),
depth kept in an R32F colour image is accepted under a depth aspect, and the
extension-requirement query allows a null list. Spare buffer sets are
imported on the first frame and never while a frame is pending, since an
import can block on GPU work that is waiting for us; host staging covers any
shortfall.
Fix two exit hazards. A game quitting without NGX shutdown left the GPU
waiting on an event that only a killed thread would set, resetting the ring
10 s later: the bridge now runs a shim hook before teardown and skips all
CUDA destroy/sync calls once Wine is shutting the process down. The shim's
own entry point skips CRT static destructors at process termination, where
locks held by killed threads made the exit spin forever.
Add a Vulkan DLSS harness (dynamic, recreate, discard, invalid, exit,
compute-queue and in-flight modes), staging/depth probes, ABI/config tests,
and an OptiScaler settings overlay for Vulkan games.
NVIDIA's enc1 and enc2 clamp the patch merge's result to ±2π (as f16) before
its e4m3 encoding (max.f16x2 / min.f16x2 with 0fC0C90FDB / 0f40C90FDB after the
merge's last MMA). The native layers did not, in either template or set:
merge_clamp (swin_common.h) now does, at the three sites that encode the merged
tokens (swin_block.h's e4m3-activation and f16 paths, swin_tok.h), and
swin_check.py's model clamps the same way.
Measured on an RX 9070 XT against a run with no native kernels (preset L,
accuracy set, the flags of Ghost of Tsushima, 1280x720 -> 3840x2160, one
frame): native enc1 alone 34 dB and enc2 alone 31 dB from the translated
kernels before, byte-identical after; the whole native set 28 -> 80 dB (the
rest is dec1, 0.09% of the output values). Preset M's fast set against
translated M: 30.8 -> 35.1 dB after 30 frames. The gfx11 path has the same
change and was not run on hardware.
The repo's tests pass (36, 3 skipped).
L's unfolded enc0 and dec0 are still ZLUDA's compile of NVIDIA's PTX and cost
2.95 ms of the preset's 5.86 ms per frame (RX 9070 XT, 1280x720 -> 4K). The
embedding, the patch expand, the Swin block (32 channels, 2 heads, at pixel
resolution) and the patch merge are now native token-lane code; the texture
work around them stays ZLUDA's compile.
enc0l_tail.hip cuts in at the barrier after the feature staging and dec0l_block.hip
before the output code that reads the block's f16 result out of the last MMAs; make_ptx.py
splices either in when D4R_TEX_SRC names it and build.sh selects both for the gfx12
native-FP8 builds. swin_tex.h carries the indexing the two tails share; swin_tok.h gains
the pixel-order token layout (SWIN_TOK_NATURAL), ZLUDA's attention key order
(SWIN_PV_ZLUDA_ORDER) and swin_tok_core, which the encoded tests reuse. swin_common.h
takes the block weight layout (moved out of swin_block.h) and the exact and approximate
reciprocal / mix forms the tails need without the HIP headers (D4R_NO_HIP_RUNTIME).
The f32 sums stay in ZLUDA's order (K slots of the FP8 WMMAs, key order of the f16
attention product) and rsqrt/rcp use the hardware approximations ZLUDA lowers to, so the
harness's final frame is byte-identical to the translated kernels: all four enc0 flag
variants, 720p and 1080p render, 1440p and 4K output, static and moving scenes, 100
frames, and the accuracy set. With M's own sum order about one token in 20,000 differed.
M's layers compile to the same instructions and its frame is byte-identical.
enc0 1.69 -> 0.63 ms and dec0 1.26 -> 0.54 ms at 720p->4K, 2.73 -> 1.16 and 2.28 -> 1.01
at 1080p->4K; the preset 5.83 -> 4.06 and 8.63 -> 5.69 ms. -DD4R_SWIN_K32_MIX rounds the
f16 products earlier for 0.56 and 0.49 ms and is not byte-identical, so it is not built by
default. The repo's tests pass (36, 3 skipped).
Shim
- Adaptive output-event wait: sleep to ~120 us before the predicted
completion (smoothed per wait site), spin near it, back off when late.
D4R_SHIM_OUTPUT_POLL_US restores a fixed interval.
- Defer the input-marker wait into the worker thread with VRAM interop
(D4R_SHIM_DEFER_MARKER, default on), taking the host setup off the
critical path.
MicroCUDA
- Experimental ZLUDA-free backend in wine_nvcuda_bridge, plus
tools/microcuda and scripts/build_microcuda.sh. Serves accuracy, fast
and native kernels; unregistered modules fall back to the reference
path. Unclassified writes invalidate prepared weights.
M kernels
- SWIN_PREP_IDENTITY_AT: key weight prep by allocation identity
(MicroCUDA); enc3 tube uses it with 8 slots. ZLUDA ignores it.
- dec2 (gfx1101): SWIN_PACKED_RESIDUAL, byte-identical, ~1.6% faster.
- Opt-in gfx12 hardware fp8 conversion (SWIN_FP8_HWCVT), unverified
on hardware.
Native K translators
- ctile: handle variants that re-materialize the parameter base (mvlo).
- rewrite_output: check live-in registers, not just outputs.
- build.sh: native input covers all 8 flag variants, output the 4 HDR
"max" variants; depth-tile skip stays mvhi-only.
Tools
- dump_runner and bench_dlss_kernel harness additions.
Alan Wake 2 uses the format for depth when enabling Ray Reconstruction.
The CPU path converts half to float in convert_row_in.
The VRAM interop path blits R16F/R16UNORM depth to an R32F conversion image before copying into the canonical buffer.
Signed-off-by: Tk-Glitch <[email protected]>
- Add docs/in-place-inputs.md: reading the DLSS inputs in place does not work,
because a hardware texture fetch on a HIP texture object over memory imported
from vkd3d-proton returns stale data while plain loads and cuMemcpy2D are
correct; records the measurements, the rule-outs and the reworked-fetch
workaround that turned out not to pay for itself.
- Add tools/hip_vk_texture_stale_repro.cpp and its build script, the native
Vulkan + HIP reproducer that rules out a general ROCm or RADV bug.
- Tag redirected surfaces as R10G10B10A2 ('R3') as well as RGBA16F ('R2'), and
let the native output kernel store the game's R10G10B10A2 texels itself.
- Import linear, exportable game textures (vkd3d-proton patch 0003) into HIP
so the output kernel writes the game's output texture with no game-side copy.
- Add ID3D12DXVKInteropDeviceD4R3 with GetVulkanLinearImageInfo.
- Gate both paths behind D4R_SHIM_OUTPUT_PACKED and D4R_SHIM_INPLACE, and use
them only for split frames of preset M whose output kernel honours the redirect.
- Convert on the host when a recorded packed frame finds the wrong kernel.
- Give the harness an R10G10B10A2 output, and add D4R_SHIM_SPIN_POLL so
frame timing is not quantised to the poll sleep.
- Document the measured removal of the output copies in performance.md.
make_ptx.py turns the 32 cvt.rn.f16x2.e4m3x2 of rrlite_dec0_4x4_folded into calls of d4r_dec_e4m3x2 (tex_common.h) when D4R_TEX_FP8=1: v_cvt_pk_f32_fp8 and two conversions to f16 instead of ZLUDA's integer lowering (about 25 instructions each; the kernel goes from 4058 to 3418 instructions). The conversion equals ZLUDA's e4m3_to_f16_bits for every code except the NaN codes 0x7f / 0xff, checked over all 65536 code pairs on an RX 9070 XT, and the head never stages those codes (it clamps to +-448 before encoding). Harness frames are byte-identical in the accuracy and the fast set, over a 16-frame moving sequence in the accuracy set. dec0 at 2227x1253 render: 0.43 -> 0.38 ms (accuracy), 0.36 -> 0.30 ms (fast). -DD4R_TEX_SOFT_E4M3 gives ZLUDA's lowering; builds without D4R_TEX_FP8 keep the PTX instruction.
On the gfx12 native-FP8 path the enc0 tail and the dec0 head encode their results with v_cvt_pk_fp8_f32 on the f32 copies of the f16 values (e4m3x4_f32 in tex_common.h) instead of ZLUDA's integer e4m3x2. The clamps keep the conversion in range: +-2pi for enc0 (the f16 bound hi[0], same in f32) and +-448 for dec0, both as a median of three, where a NaN gives the lower bound. The codes equal e4m3x2 for every non-NaN f16, checked over all 65536 patterns on an RX 9070 XT; a NaN in dec0 now encodes as -448 instead of the NaN code. Harness frames are byte-identical in both sets (LDR and HDR, motion vectors at render and display resolution). dec0: 0.21 -> 0.15 ms at 1280x720 render and 0.74 -> 0.53 ms at 2560x1440; enc0 changes by 1-2% (its time is ZLUDA's compile of the feature code). -DD4R_TEX_SOFT_E4M3 (D4R_TEX_CFLAGS) keeps e4m3x2.
The remaining two layers (96 channels, four heads) use the same swin_tok.h template: byte-identical on the same recorded launches and harness sequences, enc2 0.22 -> 0.14 ms and dec2 0.20 -> 0.14 ms at 1280x720 render, 0.75 -> 0.47 and 0.69 -> 0.47 ms at 2560x1440. Skipping windows that lie entirely outside the image is also exact but below the measurement noise (at most 2.4% of the windows of every other tube launch), so it is not kept; true16 code generation remains not tried.
swin_tok.h passes the weights as the first WMMA operand and the activations as the second, so each GEMM's encoded result is already the next one's operand and stays in registers (swin_block.h transposes every result through LDS). One wave owns one 4x4 window for the whole block: norms, attention and the MLP need no LDS and no barriers; only the patch expand and the patch merge go through an e4m3 image in LDS. Outputs are bit-identical to swin_block.h (swapped WMMA operand roles, verified on an RX 9070 XT and on recorded launches). mix2 rounds in place (accumulator is addend and destination, no copy per pair). -DSWIN_NO_TOKEN_LANES builds these layers from swin_block.h again; enc2 and dec2 are unchanged. RX 9070 XT accuracy set, 2560x1440 render: whole evaluation 8.56 -> 7.18 ms.
The 2x figure is against the previous native-FP8 layers. With NativeFp8 off
the RX 9070 XT was already at 3.37 ms for 1280x720 -> 3840x2160; the FP8 set
is 2.88 ms there.
The FP8 Swin layers encode with a clamp to +-448 (NaN-propagating packed
minimum/maximum) followed by v_cvt_pk_fp8_f32, in both the fast and the
accuracy build: equal to half_to_e4m3 for every non-NaN input, NaN encodes
as 0xff. The accuracy build rounds each k32 step with v_fma_mix (K32_MIX).
On gfx12 the accuracy builds of M's post and downsample kernels are wave64
with denormal handling kept.
RX 9070 XT, 1280x720 -> 3840x2160, D3D12 harness: the accuracy set goes
from 3.54 ms to 3.05 ms per evaluation (3.01 -> 2.59 ms under load). Its
Swin layers still equal swin_model.py on sampled blocks and its image is
byte-identical to the previous accuracy set over 8 temporal frames.
On the gfx12 native-FP8 path the Swin layers kept GEMM activations as f16
in LDS and encoded them to e4m3 again at every operand load. They are now
encoded once where they are produced, with v_cvt_pk_fp8_f32, and stored as
bytes (SWIN_A8); the patch merge reads the stored output codes. The M
downsample kernels are built as wave64 on gfx12.
RX 9070 XT, 1280x720 -> 3840x2160, D3D12 harness, DLSS 310.7: whole
evaluation 5.9 ms -> 2.8 ms of GPU time with the GPU otherwise idle, 2.4 ms
at 80-98% GPU utilisation. Harness output is byte-identical to the previous
kernels; the accuracy build matches swin_model.py on sampled blocks.
Builds without D4R_FP8_WMMA (RDNA3, and RDNA4 with NativeFp8 off) compile to
the same instructions as before.
Validate Env identifiers and accept UTF-8 BOMs and last-value INI keys.
Resolve the OptiScaler GPL license before builds and support both system
license locations. Match the bridge's KFD selection by SIMD count.
- Build the default log path from the DLL's folder, now recorded even
without d4r.ini, instead of copying the name into a MAX_PATH buffer
that a folder path of 246+ characters overflowed; no log is opened
when that folder is unknown
- Fall back to the default when a numeric setting does not start with a
decimal digit, so MarkerPollUs = -1 no longer polls every ~71 minutes;
hex values such as 0x40 still parse
- Decode RGB9E5 exponent 0 as c * 2^-24 instead of zero, and encode with
the format's real maximum 65408 (511 * 2^7) and exponents from 0
- Choose the RGB9E5 exponent by the rounded channel (< 511.5), so a
value such as 511.25 keeps the lower exponent instead of decoding as 512
An interrupted extraction or a full disk can leave an empty or truncated
.hsaco, and hipModuleLoadData can crash on it. native_override now skips
files under 64 bytes and ELF files whose section header table ends past
the end of the file; ZLUDA then compiles the PTX as usual.
- Fatbin parser: stop when an entry header runs past the declared files
size instead of wrapping the unsigned remainder and reading outside the
image.
- Output kernel table: keep the latest 64 lookups and resolve launches
newest first under its own lock, so a full table no longer drops new
lookups and a handle reused by a later output-kernel lookup resolves
with that lookup.
- Resolve a relative D4R_ZLUDA_NATIVE_DIR before linking its kernels into
the per-process directory; the relative links dangled.
- cuSurfObjectCreate: destroy the surface and return the error when its
output redirect cannot be written.
- Serve native kernels from D4R_ZLUDA_CACHE_HOME, XDG_CACHE_HOME or
~/.cache, never /tmp, where the stale-PID cleanup could follow links
planted by another user.
Reuse feature teardown on setup errors and publish output metadata only after all slots are ready. Keep render-size recovery retryable after failed restoration, and retry partially failed library loads.
Preset K, byte-identical to the previous output in all captured buffers
and the harness image, measured on an RX 7700 XT:
- LDS row padding so WMMA operand loads hit distinct banks
- WGP mode for enc1/enc2/dec1/dec2, ds_bpermute half-wave exchange for
enc0/dec0/dec3, more waves per window in enc4/dec4/dec5
- cheaper operand plumbing in common/wmma_layout.h (packed f16
conversions, no dynamic vector indexing), raw buffer weight loads,
accumulator-init vectors prepared once
- enc1 as a single-buffer recompute layer, dec1 Q/K/V chains split
- dec5 as four dependent phase kernels