Repository navigation
Description
Description
Sunshine cannot use NVENC on Linux when the CUDA/NVENC device is available but has no matching Linux DRM node.
This is reproducible with an NVIDIA GPU exposed to a Linux Hyper-V guest through GPU-P. The GPU is fully usable through /dev/dxg: NVML, CUDA, NVDEC, and direct NVENC all work, but Sunshine fails while trying to map the CUDA device to a DRM device.
The underlying limitation appears generic rather than Hyper-V-specific: Sunshine assumes that a CUDA device used for NVENC also has a corresponding DRM node under /sys/bus/pci/devices/<bus-id>/drm.
Environment
- Host: Windows 11, Hyper-V
- Guest: Gen2 Linux VM, Omarchy / Arch Linux
- Kernel:
7.1.9.arch1-2 - GPU: NVIDIA GeForce RTX 4050 via GPU-P
- NVIDIA host driver:
596.08 - GPU interface in guest:
/dev/dxg - Display device in guest:
hyperv_drm /dev/dri: onlycard1fromhyperv_drm; no NVIDIA render node- Sunshine installed version:
2026.516.143833-1
Also tested unchanged official Sunshine AppImages:
2025.924.154138(86188d47)2026.516.143833(14ffa6fd)2026.902.143621(b91470ff)
All show the same behavior.
GPU functionality outside Sunshine
The GPU-P path itself works correctly:
/dev/dxg: PASS- NVML /
nvidia-smi: PASS - CUDA: PASS
- NVDEC: PASS
- Direct NVENC API: PASS
- Direct NVENC stress test: 12,000 frames encoded successfully at 1080p, approximately 251-349 fps
So the NVIDIA hardware, driver bridge, and NVENC API are functional in the guest.
Reproduction
Configure Sunshine with NVENC and use either KMS or wlroots capture.
Example relevant setting:
encoder = nvencWith KMS capture, Sunshine reaches the CUDA/NVENC path but fails with:
Unable to find DRM device with PCI bus ID: 0000:01:00.0
The same failure occurs with wlroots capture.
CUDA reports 0000:01:00.0, but there is no corresponding NVIDIA DRM device in the guest. GPU-P exposes the GPU through /dev/dxg, not through a normal Linux DRM render node.
The only DRM card is the Hyper-V display device:
/dev/dri/card1 -> hyperv_drm
There is no matching:
/sys/bus/pci/devices/0000:01:00.0/drm
/dev/dri/renderD*
Observed behavior
Sunshine calls the DRM lookup for the CUDA device and aborts NVENC initialization when no matching DRM node exists.
This happens even though direct NVENC encoding on the same CUDA device works correctly.
Both KMS and wlroots capture hit the same limitation.
Expected behavior
If CUDA/NVENC is available but the CUDA device has no matching DRM node, Sunshine should ideally be able to fall back to a system-memory capture/upload path instead of rejecting the encoder.
Conceptually:
capture
-> system memory
-> CUDA upload
-> NVENC
This would avoid requiring DRM/DMABUF interop for devices that expose CUDA/NVENC through a non-DRM interface such as /dev/dxg.
Code-path observations
There already appears to be a DRM-free CUDA RAM path through cuda_ram_t / cuda::make_avcodec_encode_device(..., false).
The current failure appears to happen later when the encode device is initialized:
- KMS can fall back from
display_vram_ttodisplay_ram_t, but NVENC initialization subsequently attempts the CUDA-to-DRM lookup. - wlroots currently selects a VRAM-oriented CUDA path and does not appear to fall back to a RAM capture path for this case.
open_drm_fd_for_cuda_device()requires a matching DRM node and therefore cannot succeed with/dev/dxg.
PR #4946 may be relevant because it introduces a SHM/RAM capture fallback and wl::force_ram_capture. I am not claiming that it fixes this case as-is, but its RAM-capture routing looks conceptually aligned with what is needed here.
Not a regression between recent releases
I tested the same setup against Sunshine versions spanning roughly a year:
| Version | Capture | Result |
|---|---|---|
| 2025.924.154138 | KMS | fails at CUDA -> DRM lookup |
| 2026.516.143833 | KMS | same failure |
| 2026.902.143621 | KMS | same failure |
| 2026.902.143621 | wlroots | same failure |
The relevant CUDA-to-DRM assumption therefore appears older than these releases rather than being a recent regression.
Why this matters beyond this exact setup
GPU-P is only one way to reach this state. The more general case is:
A valid CUDA/NVENC device exists, but there is no corresponding Linux DRM node.
Sunshine currently cannot use NVENC in that situation even when the encoder itself works.
A generic RAM-capture/CUDA-upload fallback would make the NVENC path more resilient and avoid coupling encoder availability to the presence of a DRM peer when zero-copy DRM interop is unavailable.
I can provide additional logs or test patches if useful.
Activity
Ashutosh0x commentedon Sep 10, 2026
The DRM node isn't required by NVENC or by CUDA here — it's required by the zero-copy EGL path only, and the interesting part is that Sunshine already has a fallback for this and it fires too early to catch it.
open_drm_fd_for_cuda_device()is called from exactly one place,gl_cuda_vram_t::init()(src/platform/linux/cuda.cppL423), and the fd is used to build a GBM device and an EGL display for GL→CUDA interop:file = std::move(open_drm_fd_for_cuda_device(0)); ... gbm.reset(gbm::create_device(file.el)); display = egl::make_display(gbm.get());There are two CUDA encode-device factories, and only one of them needs that:
cuda::make_avcodec_gl_encode_device()->gl_cuda_vram_t— needs the NVIDIA DRM node (kmsgrab L1746,display_vram_t)cuda::make_avcodec_encode_device()->cuda_ram_t/cuda_vram_t— no DRM node at all (kmsgrab L1568,display_ram_t; also x11grab L660)kms_display()already falls back from vram to ram (L1911-1928):But that fallback is keyed on
display_vram_t::init(). On GPU-P that init succeeds — the guest does have a DRM card (hyperv_drmcard1) for capture. The lookup that fails is for the CUDA device's own node, and it happens later, inmake_avcodec_encode_device(pix_fmt), after the fallback decision is already made. So a case the fallback was written for surfaces as a hard failure instead.That also explains why the PCI bus ID in the message is the NVIDIA card while capture itself was fine.
Two directions, depending on what maintainers prefer: let an encode-device failure on the vram display fall back to
display_ram_tthe same way an init failure does, or probe the CUDA device's DRM node duringdisplay_vram_t::init()so the existing fallback sees it.Read from source rather than reproduced — I don't have a GPU-P setup. If you can run it,
display_ram_ton this same host should already work, so forcing the RAM path is worth trying as a stopgap.RoYaL69 commentedon Nov 6, 2026
Thanks @Ashutosh0x, the analysis checks out. Retested on the same GPU-P guest (RTX 4050, host driver 32.0.16.1060 / KMD 610.60,
/dev/dxg, onlyhyperv_drmcard1, no NVIDIA DRM node).wlr capture is fixed by the
v2026.1103.235308pre-release (via #5873 + #4946):2026.906.222525capture = wlr,encoder = nvencUnable to find DRM device with PCI bus ID: 0000:01:00.0->Encoder [nvenc] failed2026.1103.235308(AppImage)capture = wlr,encoder = nvenc[wlgrab] CUDA capture path: GPU -> RAM -> GPU bridge (cross-GPU system)->Found H.264 encoder: h264_nvenc,Found HEVC encoder: hevc_nvenc,Found AV1 encoder: av1_nvencThe bridge is selected because the render node's PCI id can't be read (
Could not read PCI bus id for [/dev/dri/renderD128], assuming cross-GPU capture), and SHM screencopy covers the missing DMA-BUF support. This is the startup encoder probe; I haven't run a full Moonlight session yet.KMS is unchanged in that pre-release:
kms_display()still only falls back todisplay_ram_twhendisplay_vram_t::init()fails, socapture = kms+nvencstill ends in the DRM lookup, as described above. Either of the two directions you proposed would still be needed for KMS.Separate small bug found on the way: when
cuInitfails (here: guest driver files out of sync with the host driver ->CUDA_ERROR_NO_DEVICE), Sunshine segfaults right afterCreating encoder [h264_nvenc]instead of logging the error. Insrc/platform/linux/cuda.cppcheck(),name/descriptionare uninitialized and used even ifcuGetErrorName/cuGetErrorStringfail; the crash isstrlen(0x1)under"Couldn't initialize cuda: ". Initializing both tonullptr/a fallback string (or checking the return codes) would turn that into a normal encoder failure.ReenigneArcher commentedon Nov 6, 2026
@RoYaL69, could you test #6056 on your GPU-P setup and check whether it fixes the segfault you reported when
cuInitfails? Sunshine should now log the numeric CUDA error, with safe fallback text if the error lookups fail, and report initialization failure instead of crashing. Please share the result and relevant log output.This PR addresses the logging crash; the KMS capture fallback problem still needs a separate fix.
RoYaL69 commentedon Nov 13, 2026
@ReenigneArcher, tested #6056 (head f9e124d) against master (774be88) using the CI AppImages, on an Arch Linux Hyper-V guest with GPU-P (RTX 4050 via
/dev/dxg, wsl-style libcuda,capture = wlr,encoder = nvenc).To force
cuInitto fail I used a stublibcuda.so.1viaLD_LIBRARY_PATH:cuInitreturns 100 (CUDA_ERROR_NO_DEVICE), andcuGetErrorName/cuGetErrorStringfail without setting their out pointers.Creating encoder [h264_nvenc].Error: Couldn't initialize cuda: Unknown CUDA error (100): No error description availablefollowed byInfo: Encoder [nvenc] failed. Sunshine then falls back to the next encoders (Found H.264 encoder: libx264 [software]) and does not crash./usr/lib/gpup/lib): the NVENC probe still works with the PR. It findsh264_nvenc,hevc_nvencandav1_nvenc, and[wlgrab] CUDA capture path: GPU -> RAM -> GPU bridge (cross-GPU system)is logged as before.Caveats: this is a stub reproduction rather than a genuinely out-of-sync guest driver, and I only tested the encoder probe, with no streaming client. The fix looks good to me.