Skip to content

DLSS-NR: probe the float vtable slot on Vulkan, as D3D12 does - #44

Open
RobertRautenbach wants to merge 1 commit into
Dagherbou:dlss-neural-renderingfrom
RobertRautenbach:fix/dlssnr-vk-float-slot
Open

DLSS-NR: probe the float vtable slot on Vulkan, as D3D12 does#44
RobertRautenbach wants to merge 1 commit into
Dagherbou:dlss-neural-renderingfrom
RobertRautenbach:fix/dlssnr-vk-float-slot

Conversation

@RobertRautenbach

Copy link
Copy Markdown

Summary

The Vulkan path never works out which vtable slot the NGX parameter block keeps floats in, so every float it hands the model is discarded. Run the same probe the D3D12 path already runs.

Problem

The forwarder writes floats into the parameter block through a raw vtable index, starting at the slot the public header declares:

int g_floatSlot = 1;

void setFloat(void *params, const char *name, float v) {
    void **vt = *reinterpret_cast<void ***>(params);
    reinterpret_cast<PFN_SetFloat>(vt[g_floatSlot])(params, name, v);
}

The driver's own capability block does not keep floats there, as the forwarder's own comment records: a float written to slot 1 reads back as FAIL_UnsupportedParameter while every uint lands. dlssnr/FORWARDER_INVESTIGATION.md in this tree puts the real one at 6:

The float setter lives at vtable slot 6 on the driver's capability block, same as the forwarder path finds. So the block is being driven correctly.

DlssNr_Dx12.cpp therefore resolves dlssnr_call_probe_float and dlssnr_call_set_float_slot and runs DiscoverFloatSlot before it writes anything. DlssNrFeature_Vk.cpp resolves neither export and never probes, so on Vulkan the index stays at 1 and every setFloat is thrown away:

  • DLSSNR.MVecScaleX and DLSSNR.MVecScaleY, on every evaluate
  • DLSSNR.Intensity, DLSSNR.LocalStructureStrength, DLSSNR.LocalToneStrength and DLSSNR.SkinStructureStrength, at create and on every evaluate

The uint parameters were unaffected, which is why the pass otherwise looked healthy. Settings carried in DlssNrConstants are unaffected too, since they never go through the block.

Changes

One file, 46 added lines, nothing removed:

  • Resolve dlssnr_call_set_float_slot and dlssnr_call_probe_float in LoadForwarder. Both optional.
  • Add DiscoverFloatSlot, a port of the D3D12 one: same probe key, same candidate order, same 0.375f round-trip sentinel.
  • Call it on g_vk.capabilityParams immediately after allocation, before the first write.

Result

The motion vector scale is the one that shows. Without it the model reprojects its history with no idea how the game encodes its vectors, which is wrong only while something is moving and exact while it is still.

In Baldur's Gate 3 on the Vulkan renderer that presented as shadows shimmering slightly darker whenever the camera turned. It survived DLAA, it survived #42, and it survived every host-side setting, because the host arithmetic was always correct and the value simply never arrived. With the probe in place it is gone, and the log reports the slot it found.

The four tuning sliders were inert on Vulkan as well, and now take effect.

Relationship to #42

This is a precondition for #42 on Vulkan rather than an alternative to it. #42 computes the correct motion vector metadata and forwards the scale; the scale is a float, so on Vulkan it was being discarded after being correctly computed. That both of #42's Vulkan boxes are unticked is consistent with this. The two changes are independent, and this one applies to dlss-neural-rendering on its own.

Testing

  • DLSS SR with Vulkan
  • DLAA with Vulkan
  • D3D12 — out of reach of this change; that path already probed
  • Windows — not tested

Linux, Proton (proton-cachyos, Wine 11), RTX 5080, driver 610.57.04, Baldur's Gate 3 at 3840x2160 on the native Vulkan renderer with VulkanUpscaler=dlss. Exercised at three super-resolution ratios (2953x1661, 2560x1440, 2258x1270) and at DLAA; the model received -1 x -1 in each, and the probe reported slot 6. Built with this repo's unsigned workflow. Formatting checked against .clang-format: the file's pre-existing complaint count is unchanged and none falls on an added line.

Default-identical where the forwarder lacks the two exports: the index keeps the header's value and this path behaves exactly as before. No Vulkan resource is created, freed or resized, and nothing here runs when neural rendering is off.


Developed with AI assistance (Claude). I tested it in game and reviewed the change before submitting.

The forwarder writes floats to the NGX parameter block through a raw vtable
index, g_floatSlot, which defaults to the slot the public header declares.
The driver's own block does not keep floats there: one written to it reads
back as FAIL_UnsupportedParameter while every uint lands. The D3D12 path
resolves dlssnr_call_probe_float and dlssnr_call_set_float_slot and round
trips a value through each candidate slot before writing anything.

The Vulkan path resolved neither export and never probed, so every float the
model is given went into the discarding slot: DLSSNR.MVecScaleX and
MVecScaleY on each evaluate, and Intensity, LocalStructureStrength,
LocalToneStrength and SkinStructureStrength at create. The motion vector
scale is the one that shows, since without it the model reprojects its
history with no idea how the game encodes its vectors.

Run the same probe on g_vk.capabilityParams before the first write.

- Both forwarder calls are optional. Where they are absent the slot keeps the
  header's value and this path behaves exactly as it did before.
- g_floatSlot is a single global inside the forwarder, so a process running
  both paths probes twice and agrees with itself.
- No Vulkan resource is created, freed or resized, and nothing here runs when
  neural rendering is off.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant