NVIDIA-RTX / NVIDIA-RTX/Streamline
sl.common: per-Present QueryVideoMemoryInfo on a sleeping NVIDIA adapter causes ~150 ms stalls when rendering on another GPU (laptop + eGPU)
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 1.2k
- Forks
- 159
- PR merge metrics
- No merged PRs in 30d
Description
Summary
presentCommon() in sl.common calls QueryVideoMemoryInfo() on every Present. It calls it on ctx.adapter, which is set on the first frame to "best NVDA adapter". There is no check that this adapter is the adapter the app renders on.
On a laptop with an NVIDIA dGPU and an AMD eGPU, the game renders on the eGPU and Windows keeps the idle dGPU in D3. Each query then blocks the render thread on the dxgkrnl adapter core lock while the GPU power arbiter wakes the dGPU. Result: stalls of about 145 ms, several per second, at any graphics settings.
Environment
- Game: Diablo IV (Steam), ships Streamline 2.11.1 (
sl.common.dll,sl.interposer.dll,sl.dlss.dll,sl.dlss_g.dll,sl.reflex.dll,sl.pcl.dll) - Laptop: Intel i7-11370H, Intel Iris Xe, NVIDIA RTX 3050 Ti Laptop GPU (idle, runtime D3)
- eGPU: AMD Radeon RX 7900 XTX over Thunderbolt, display connected to the eGPU. The game renders on the AMD GPU (game log:
Gpus detected (index 0 will be used): AMD Radeon RX 7900 XTX). - Windows 11 build 26200
Evidence
- PresentMon, 99 s: 1847 frames, average 18.6 FPS. 378 frames > 50 ms. On slow frames the CPU-side time is 181 ms and GPU busy is 15.7 ms, so the GPU is idle while it waits.
- WPR capture of 60 s, with CPU and context switch stacks. The render thread waited 174 times, 145 ms on average (25.3 s in total). WaitReason =
WrResource. Wait stack:
Readying stack (the thread that released the lock):ntoskrnl!ExAcquireResourceSharedLite dxgkrnl!DXGADAPTER::AcquireCoreResourceShared dxgkrnl!DxgkQueryVideoMemoryInfo win32u!NtGdiDdDDIQueryVideoMemoryInfo dxgi!CDXGIAdapter::QueryVideoMemoryInfo sl.common.dll sl.interposer.dll
The main thread and the job workers then wait on the render thread, so the whole game stalls.dxgkrnl!DXGADAPTER::ReleaseCoreResource dxgkrnl!DXGADAPTER::ReleaseCoreSync dxgkrnl!DxgkReleaseAdapterCoreSync dxgkrnl!DpiPowerArbiterThread - The RTX 3050 Ti was in D3 at idle.
- Workaround confirmed: after I disabled the RTX 3050 Ti in Device Manager, the stutter stopped completely.
The trace does not show the adapter LUID of each query. The link to the dGPU is inferred from points 3 and 4 and from the source below.
Relevant source
v2.11.1, and the same code is on main: source/plugins/sl.common/commonInterface.cpp
// presentCommon()
else if (ctx.adapter)
{
//! IMPORTANT: Overhead for calling 'QueryVideoMemoryInfo' is 0.01ms
ctx.adapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &videoMemoryInfo);
// First run, find best NVDA adapter
if (isVendorNvidia(ctx.sysCaps.adapters[i].vendor) && ctx.sysCaps.adapters[i].architecture > hwArch)
ctx.adapter = reinterpret_cast<IDXGIAdapter3*>(ctx.sysCaps.adapters[i].nativeInterface);
The 0.01 ms assumption holds for an active GPU. It does not hold for a laptop dGPU in runtime D3.
Suggested fix
- Select
ctx.adapterby matching the LUID of the app's device (for exampleID3D12Device::GetAdapterLuid()), not only by vendor and architecture. If the app does not render on an NVIDIA adapter, skip the VRAM budget query. This is the same path that an AMD-only system already takes, becausectx.adapterstays null there. - Optionally, do not call
QueryVideoMemoryInfoon the Present path, or call it less often.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read source/plugins/sl.common/commonInterface.cpp, focusing on presentCommon() and the code that initializes ctx.adapter. Trace how the rendering device identity can be compared with the selected adapter, then validate the behavior on the laptop/eGPU setup described. Done means an unrelated sleeping adapter is not queried on the Present path and the reported stalls are absent.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100