Files
ayo 694a7a0126 Add experimental native Vulkan DLSS frontend
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.
2026-10-09 02:57:23 -04:00

193 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Experimental native Vulkan support
The development shim now implements the Vulkan NGX Super Resolution API in
addition to D3D12. Wolfenstein: Youngblood is the intended first game test;
its gameplay, image quality, and performance have not been validated here.
Existing release archives do not acquire this support until rebuilt.
Build with `scripts/build_d4r_nvngx_shim.sh`. The result is
`build/d4r_nvngx.dll`; use it as `d4r/nvngx.dll` in a development install.
Keep NVIDIA's actual CUDA NGX core at `d4r/ngx/_nvngx.dll` and the official
DLSS feature at `d4r/nvngx_dlss.dll`. The existing Wine CUDA bridge,
ZLUDA, ROCm runtime, kernels and `d4r.ini` are still required.
## Youngblood testing
Route the game's Vulkan NGX calls through OptiScaler's native Vulkan DLSS
backend and point that backend at the new shim. Apply these values to an
existing OptiScaler installation, retaining its other settings:
```ini
[Upscalers]
VulkanUpscaler=dlss
[DLSS]
Enabled=true
[Libraries]
NvngxPath=d4r\nvngx.dll
[Inputs]
EnableDlssInputs=true
[Hooks]
HookOriginalNvngxOnly=true
[Spoofing]
Vulkan=true
VulkanExtensionSpoofing=false
[FrameGen]
Enabled=false
FGInput=nofg
FGOutput=nofg
```
Keep `HookOriginalNvngxOnly=true`. Setting it to false causes OptiScaler to
intercept the shim's own `_nvngx.dll` load and return the proxy instead of the
CUDA NGX core. Apply `packaging/optiscaler-vulkan.settings` with
`scripts/configure_optiscaler.py` to an existing INI to preserve the shared
routing requirements and the game's separate plugin/spoofing choices.
OptiScaler must actually be loaded into the game. The D3D12 release's
`dxgi.dll` proxy and its DXGI spoofing alone are not proof of Vulkan hook
activation or DLSS eligibility. Use the proxy/override supported by your
OptiScaler installation for Youngblood, and enable DLSS in the game's settings.
The patched `d3d12.dll` and `d3d12core.dll` are unnecessary for native Vulkan.
Do not select a Vulkan-to-D3D12 upscaler backend for this test.
The shim log should contain `native Vulkan NGX ready` and a successful
`NVSDK_NGX_CUDA_CreateFeature`. OptiScaler's log should identify the native
Vulkan DLSS backend and `d4r\nvngx.dll`. If DLSS is unavailable or output
fails, retain both logs; initialization alone does not establish rendering.
## Synchronization and limits
The frontend records sampled-image compute reads, a device event indicating input
completion, a wait on a host-signaled output event, and a storage-image compute write into
the caller's still-recording command buffer. An independent CPU waiter watches each input
event, the existing CUDA worker evaluates DLSS, and the waiter signals
output only after the CUDA result has been downloaded. Output belongs to the
same frame. The frontend never ends or submits the game's command buffer.
This first implementation uses host-coherent CPU staging, with format
conversion on the GPU. It is slower than the D3D12 VRAM path. Its event protocol needs no
NVIDIA-specific or external-memory Vulkan extensions and works with Vulkan
1.0 event/compute APIs. Staging buffers and descriptors are reclaimed only after
a GPU event following the output shader confirms consumption, rather than after
CPU completion. Evaluation and feature release do not call `vkDeviceWaitIdle`
on the game's concurrently submitted queues. At most eight recorded evaluations
per feature may be outstanding.
Completed staging buffers, descriptors and events are cached per feature, and
reused only after CPU production and GPU consumption finish and the same command
buffer is being recorded again. This removes per-frame buffer allocation overhead.
Staging prefers CPU-cached coherent memory, because scalar reads from uncached
mapped memory are expensive. CUDA's RGBA16F output is copied without expansion;
the output shader decodes its packed half values when writing the game's image.
GPU validation covers all 63,488 finite half encodings, including signed zero and
subnormals. This removes the CPU half-to-float expansion measured at about
28–29 ms per 2560×1440 DOOM frame. Output/image parity for every game is not implied.
Requirements and limitations:
- Use a Vulkan 1.1 instance (or enable `VK_KHR_get_physical_device_properties2`
for Vulkan 1.0 adapter identity queries). Evaluate outside render passes/dynamic
rendering, on a compute-capable queue. Building the shim requires `glslangValidator`.
- Inputs use `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` and `SAMPLED` usage;
output uses `VK_IMAGE_LAYOUT_GENERAL` and `STORAGE` usage. These follow the
[NVIDIA DLSS resource contract](https://raw.githubusercontent.com/NVIDIA/DLSS/main/doc/DLSS_Programming_Guide_Release.pdf).
No image layout transitions or transfer usages are needed. A developer caller
explicitly using GENERAL for all inputs can set `D4R_VULKAN_INPUT_GENERAL=1`.
- Images must be single-sample 2D views with one mip and one array layer.
The caller must provide accurate metadata and maintain queue ownership.
- Input sampling converts float, half, UNORM and packed formats directly into
canonical RGBA16F color, RG16F motion, and R32F depth/exposure staging. Half
conversion uses round-to-even and preserves finite subnormals. Combined depth/stencil formats
require a depth-only sampled view; stencil is untouched. Storage outputs support
RGBA16F, RGBA32F, R11G11B10F, A2B10G10R10 and RGBA8 UNORM. Packed R11/R10
outputs require `shaderStorageImageExtendedFormats` on the existing device.
Unsupported storage formats are rejected.
- Output must match the feature's full output dimensions. Output subrect offsets,
transparency masks and bias color masks are rejected rather than ignored.
- Each recorded evaluation is for one submission. Reset/discard command buffers
before re-recording; never resubmit an evaluation or submit it after releasing
its feature. The frontend reclaims its staging objects after completed batches.
- Release cancels waiting jobs and drains CPU/CUDA work; unfinished GPU objects
remain retained for completion or shutdown. Shutdown requires the application
to externally synchronize its queues and discard unsubmitted recordings before
the final device-idle cleanup. Never submit recordings after feature release.
- An unsubmitted input times out after 30 seconds (`D4R_VULKAN_SUBMIT_TIMEOUT_MS`).
Timeout/CUDA failure clears and signals the output to unblock submitted GPU
work, logs the failure, and makes the next evaluation fail until recreation.
- One Vulkan device per shim instance. Do not mix native Vulkan and D3D12 handles.
- Motion-vector dilation requires the existing VRAM path and is unavailable here.
DLSS Frame Generation and Ray Reconstruction remain unsupported.
The resource ABI and initialization signatures follow
[NVIDIA's Vulkan headers](https://github.com/NVIDIA/DLSS/blob/main/include/nvsdk_ngx_vk.h)
and the NGX core signatures used by
[OptiScaler's proxy](https://github.com/optiscaler/OptiScaler/blob/master/OptiScaler/proxies/NVNGX_Proxy.h).
Host event signaling follows the
[Vulkan event synchronization contract](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdWaitEvents.html).
## Synthetic harness
The harness creates an AMD Vulkan device without NGX-specific device extensions,
records constant color/depth/motion inputs, evaluates DLSS, and reads the result
back after the same command buffer completes. Every submitted frame must contain
nonzero finite RGBA16F output. It also supports invalid resource and discarded
command buffer tests.
```sh
scripts/build_vulkan_dlss_harness.sh
scripts/run_vulkan_dlss_harness_proton.sh PROTON_DIR PORTABLE_D4R_DIR build/vulkan.rgba16f 3
scripts/run_vulkan_dlss_harness_proton.sh PROTON_DIR PORTABLE_D4R_DIR build/unused.rgba16f 1 invalid
scripts/run_vulkan_dlss_harness_proton.sh PROTON_DIR PORTABLE_D4R_DIR build/unused.rgba16f 1 discard
```
The runner uses an isolated prefix at `/tmp/d4r-vulkan-harness-compat`; override
`D4R_PROTON_COMPAT_DATA` to choose another test prefix. It reads the supplied
runtime and does not install into any game or existing game prefix. The shim
log is `build/vulkan-harness.log`. A harness pass is synthetic execution proof,
not Youngblood acceptance.
Local validation on 2026-10-08: 38 tests pass (three optional motion GPU tests
skipped), including the x64 resource ABI and configuration regressions. The
corrected K Vulkan harness passes 16 normal and 24 dynamic-resolution frames
with finite, nonzero output on RX 7700 XT/GE-Proton11-3, plus discarded-recording
release and invalid-resource rejection. The harness accepts Proton's NVIDIA-spoofed
adapter identity and uses DOOM's NGX app ID/SDK version. This is fixture execution
proof; Youngblood rendering still needs separate validation. The packed input
path additionally passes 64 dynamic frames, 64 frames with 15 feature recreations
across quality modes, and discarded/invalid recording checks. Its final dynamic
output is byte-for-byte identical to the preceding CPU-conversion implementation.
DOOM Eternal has produced verified nonblack menu output at 2560×1440 on this
machine. User-reported frame rates improved from 6 to 17, then 33 and 42 FPS during
staging fixes; these are not controlled benchmarks. The latest packed-input
optimization still needs an in-game comparison. Graphics-ring resets were also
observed, including after the latest game session was closed normally; stability
and exit behavior remain unresolved. Synthetic passes do not establish long-term
gameplay stability or visual parity.
Check the current sampled/storage staging independently of CUDA/NGX:
```sh
bash scripts/build_vulkan_staging.sh
c++ -std=c++20 -O2 -Ibuild/vulkan-staging tools/vulkan_staging_probe.cpp -lvulkan -o build/vulkan_staging_probe
build/vulkan_staging_probe
```
All six input and five output format checks pass on RX 7700 XT/RADV, with
correct odd-width pixels and no Khronos validation errors. Inputs have no
TRANSFER_SRC usage; output writes need no TRANSFER_DST usage. This covers
DOOM's R11G11B10 color/output, RG16F motion, R16F exposure and D32/S8 depth.
Depth/stencil staging can be checked independently of CUDA/NGX with:
```sh
c++ -std=c++20 -O2 tools/vulkan_depth_copy_probe.cpp -lvulkan -o build/vulkan_depth_copy_probe
build/vulkan_depth_copy_probe
```
On the RX 7700 XT/RADV, D16/S8 and D32/S8 passed for depth-only and combined
view masks, with every sample matching the cleared depth value. D24/S8 was
reported unsupported by this driver and skipped. This proves the depth-copy
contract, not DOOM's complete DLSS evaluation.