fix: route Streamline's own export calls to the loader (DLSS-G crash on Wine/Proton) - #2
Open
RobertRautenbach wants to merge 1 commit into
Open
Conversation
…on Wine/Proton) On Wine, vulkan-1.dll is a forwarder into winevulkan, which serves its device dispatch table from the same addresses it exports. Streamline's table therefore points at our export detours, and sl.common's own vkGetSwapchainImagesKHR - issued from inside DLSS-G's swapchain clone - takes the game's view and re-enters that clone before it has filled its proxy buffers. DLSS-G reads an empty vector, falls back to a NULL and faults at sl.dlss_g+0x55C35, about two seconds into the world, every launch. sl.log stops at "cloneFakeBuffers: ENTRY" and fgvk.log's last line is the export being routed to the game's view. ForceReal cannot catch it: the game resolves through GIPA and never enters these hooks, so no re-entry scope is pushed when Streamline calls them. The three swapchain exports now send callers that are Streamline itself to the loader. IsStreamlineModule() in slmodule.h matches sl.*, nvngx_dlssg and NvLowLatencyVk on the module file name and is unit-tested; OptiScaler's nvngx.dll shim is deliberately not matched, so its hooks keep the game's view. Windows is unaffected - the Khronos loader keeps lookup and dispatch apart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Under Wine/Proton, Streamline's own device dispatch table aliases the four vulkan-1 export detours, so
sl.commoncalls them and receives the game's view. DLSS-G then faults about two seconds into the world, every launch. This sends callers that are Streamline itself to the loader on the three swapchain exports, leaving every other caller exactly where it is.With this, DLSS-G runs:
FG stats: 300 presents -> 600 frames displayed (x2.00) status=0, sustained, on Baldur's Gate 3's native Vulkan renderer under Proton.The bug
On Wine,
vulkan-1.dllis a pure forwarder intowinevulkan, andwinevulkanserves its device dispatch table from the same addresses it exports. I checked this byte-exactly rather than assuming it:vkGetSwapchainImagesKHRexport RVA0x1ac70appears exactly once in the image, at.rdata 0x2eee8, immediately preceded by a pointer to the string"vkGetSwapchainImagesKHR". Same forvkAcquireNextImageKHRandvkQueuePresentKHR. The Khronos loader on Windows keeps lookup and dispatch apart, soGetProcAddress(vulkan-1, X)andvkGetDeviceProcAddr(dev, X)differ there and this never reproduces.Because
InstallVkHooksdetours those exported bodies in place, and Streamline builds its table fromGetProcAddress(vulkan-1, "vkGetDeviceProcAddr"),sl.common'sm_ddt.GetSwapchainImagesKHRish_GetSwapchainImagesKHRExport.sl.common::Vulkan::getSwapChainBufferissues the usual two-call count-then-fill query, from inside DLSS-G'scloneFakeBuffers. That re-enters the clone, which has already resized its real back-buffer vector but has not yet calledresizeProxyBuffers, so the proxy vector is still empty. The copy loop computescount = (end - begin) / 8 = 0, takes its out-of-range fallback, and dereferences it:Stack:
sl.dlss_g+0x55C35<-sl.interposer+0x366DB<-fgvk+0x289C.sl.logstops atcloneFakeBuffers: ENTRY - width=3840, height=2160, format=44, bufferCount=6, andfgvk.log's last line isexport vkGetSwapchainImagesKHR from tid=328 routed to the game's (fake-buffer) view. Neither ofcloneFakeBuffers' own failure exits logs, so nothing actually failed - the code observed its own half-built state.ForceReal()cannot catch this.bg3.exeimports novulkan-1.dll(I dumped its import and delay-import directories) and resolves through GIPA, so it never enters these hooks and no re-entry scope is ever pushed when Streamline calls them. The same holds on DLSS-G's pacer thread, where any thread-local guard is structurally zero.Changes
src/slmodule.h:IsStreamlineModule(), a pure predicate matchingsl.*,nvngx_dlssgandNvLowLatencyVkon the module file name.src/tests/slmodule_test.cpp: 13 cases, wired intotests_main.cppand thefgvk-teststarget.src/vkhooks.cpp:CallerIsStreamline(_ReturnAddress())resolves the calling module, and the three swapchain export hooks OR it into the existingForceReal()test.BUILD.md: a paragraph in Script Extender coexistence, since this is the same "which caller gets which view" question.Deliberately not a bare
nvngxprefix. OptiScaler ships annvngx.dllDLSS shim next to the exe; capturing it would put its hooks on the driver's present under the pacer thread, which is the deadlock class already fixed inf624005f. There is a test asserting it is excluded.Verification
Proton (proton-cachyos, Wine 11), NVIDIA 610.57.04, RTX 5080, Baldur's Gate 3 at 3840x2160, native Vulkan, Streamline 2.12.0, alongside OptiScaler (DLSS SR + Neural Rendering) and BG3SE in-process.
x2.00 status=0, no present failures, no watchdog stalls, clean exit.export vkGetSwapchainImagesKHR from tid=328 routed to the game's (fake-buffer) viewstill appears, andexport vkGetDeviceProcAddr(vkQueuePresentKHR) inside loader vkCreateDevice (third-party hook) -> game viewstill fires for OptiScaler. The OptiScaler menu and BG3SE mod UI both still draw.fgvk-tests: 27/27 pass, on Linux and on windows-latest/MSVC.I could not test on Windows, and by construction there is nothing to test: the two addresses differ there, so
CallerIsStreamlineis only ever consulted for callers that already took theForceRealpath or are genuinely third parties. If you would rather it were impossible to reach on Windows at all, it is easy to gate.Happy to attach the full
fgvk.log/sl.logpairs from before and after, or to test any variant you would prefer - I have the environment set up.Developed with AI assistance. I ran it, tested it in game and reviewed the change before submitting. Noting it because this touches a crash path you cannot reproduce, so the evidence above is what it rests on, not my say-so.