From 6a588a29b2bf2ca02824adca9b784e9fe26b78d9 Mon Sep 17 00:00:00 2001 From: neatnoise Date: Wed, 8 Apr 2026 15:58:42 +0200 Subject: [PATCH 1/4] vulkan: auto-detect GPU with connected display instead of hardcoding renderD128 On multi-GPU systems the Vulkan encoder defaulted to /dev/dri/renderD128 which may not be the GPU driving the display. This caused encode failures or unnecessary cross-GPU copies. Add platf::find_render_node_with_display() which scans DRM connectors to find the GPU with a connected monitor and returns its render node path. Use this as the default when adapter_name is not configured. Fallback chain: user config > auto-detected GPU > renderD128 > FFmpeg default. --- src/platform/common.h | 2 + src/platform/linux/misc.cpp | 55 ++++++++++++++++++++++++++++ src/platform/linux/vulkan_encode.cpp | 4 +- src/platform/macos/misc.mm | 4 ++ src/platform/windows/misc.cpp | 4 ++ src/video.cpp | 11 ++++-- 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index a114af709e3..dc7be4ed14f 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -861,6 +861,8 @@ namespace platf { */ std::string get_host_name(); + std::string find_render_node_with_display(); + /** * @brief Gets the supported gamepads for this platform backend. * @details This may be called prior to `platf::input()`! diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index ba40a802873..df6d7f365ab 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -38,6 +38,12 @@ #include #include +#ifdef SUNSHINE_BUILD_DRM + #include + #include + #include +#endif + // local includes #include "graphics.h" #include "misc.h" @@ -1163,4 +1169,53 @@ namespace platf { std::unique_ptr create_high_precision_timer() { return std::make_unique(); } + + std::string find_render_node_with_display() { +#ifdef SUNSHINE_BUILD_DRM + auto *dir = opendir("/dev/dri"); + if (!dir) { + return {}; + } + + std::string result; + while (auto *entry = readdir(dir)) { + if (strncmp(entry->d_name, "card", 4) != 0 || !isdigit(entry->d_name[4])) { + continue; + } + + std::string path = std::string("/dev/dri/") + entry->d_name; + int fd = open(path.c_str(), O_RDWR); + if (fd < 0) { + continue; + } + + auto *res = drmModeGetResources(fd); + if (res) { + for (int i = 0; i < res->count_connectors && result.empty(); i++) { + auto *conn = drmModeGetConnector(fd, res->connectors[i]); + if (conn) { + if (conn->connection == DRM_MODE_CONNECTED) { + char *render = drmGetRenderDeviceNameFromFd(fd); + if (render) { + result = render; + free(render); + } + } + drmModeFreeConnector(conn); + } + } + drmModeFreeResources(res); + } + close(fd); + if (!result.empty()) { + break; + } + } + closedir(dir); + return result; +#else + return {}; +#endif + } + } // namespace platf diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index 31927179de4..bb114241074 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -79,7 +79,9 @@ namespace vk { static int create_vulkan_hwdevice(AVBufferRef **hw_device_buf) { // Resolve render device path to Vulkan device index - if (auto render_path = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name; render_path[0] == '/') { + auto detected = platf::find_render_node_with_display(); + auto fallback = detected.empty() ? std::string("/dev/dri/renderD128") : detected; + if (auto render_path = config::video.adapter_name.empty() ? fallback : config::video.adapter_name; render_path[0] == '/') { if (auto idx = find_vulkan_index_for_render_node(render_path.c_str()); !idx.empty() && av_hwdevice_ctx_create(hw_device_buf, AV_HWDEVICE_TYPE_VULKAN, idx.c_str(), nullptr, 0) >= 0) { return 0; } diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index c678d80c504..89edae10ca0 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -560,6 +560,10 @@ operator bool() override { std::unique_ptr create_high_precision_timer() { return std::make_unique(); } + + std::string find_render_node_with_display() { + return {}; + } } // namespace platf namespace dyn { diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 40a23f00cbf..fb94860127b 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1791,4 +1791,8 @@ namespace platf { return true; } + + std::string find_render_node_with_display() { + return {}; + } } // namespace platf diff --git a/src/video.cpp b/src/video.cpp index 67234bc112d..45c6201bc8a 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2994,9 +2994,10 @@ namespace video { return hw_device_buf; } - auto render_device = config::video.adapter_name.empty() ? nullptr : config::video.adapter_name.c_str(); + auto detected = platf::find_render_node_with_display(); + auto render_device = config::video.adapter_name.empty() ? detected.c_str() : config::video.adapter_name.c_str(); - auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VAAPI, render_device, nullptr, 0); + auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VAAPI, render_device[0] ? render_device : nullptr, nullptr, 0); if (status < 0) { char string[AV_ERROR_MAX_STRING_SIZE]; BOOST_LOG(error) << "Failed to create a VAAPI device: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); @@ -3019,8 +3020,10 @@ namespace video { return hw_device_buf; } - // Try render device path first (like VAAPI does), then fallback to device indices - auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); + // Try render device path first, auto-detecting the GPU with a connected display + auto detected = platf::find_render_node_with_display(); + auto fallback = detected.empty() ? std::string("/dev/dri/renderD128") : detected; + auto render_device = config::video.adapter_name.empty() ? fallback.c_str() : config::video.adapter_name.c_str(); auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VULKAN, render_device, nullptr, 0); if (status >= 0) { From f74a4da68a43a4713a7b39a5f0070d1a8004544e Mon Sep 17 00:00:00 2001 From: neatnoise Date: Thu, 16 Apr 2026 17:42:34 +0200 Subject: [PATCH 2/4] refactor: consolidate render device resolution into platf::resolve_render_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace duplicated adapter_name → render_device resolution logic across video.cpp, vaapi.cpp, and vulkan_encode.cpp with a single platf::resolve_render_device() function. The resolution order is: 1. config::video.adapter_name if set by user 2. Auto-detected GPU with connected display (find_render_node_with_display) 3. /dev/dri/renderD128 fallback find_render_node_with_display() is now internal to linux/misc.cpp, removed from the public platf:: API and platform stubs. --- src/platform/common.h | 9 ++++++++- src/platform/linux/misc.cpp | 8 ++++++++ src/platform/linux/vaapi.cpp | 8 +++----- src/platform/linux/vulkan_encode.cpp | 5 ++--- src/platform/macos/misc.mm | 2 +- src/platform/windows/misc.cpp | 2 +- src/video.cpp | 11 ++++------- 7 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index dc7be4ed14f..7a15cb09cf0 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -861,7 +861,14 @@ namespace platf { */ std::string get_host_name(); - std::string find_render_node_with_display(); + /** + * @brief Resolves the render device path to use for hardware encoding. + * @details If `config::video.adapter_name` is set, returns that. + * Otherwise, auto-detects the GPU with a connected display via `find_render_node_with_display()`. + * Falls back to `/dev/dri/renderD128` if detection fails. + * @return Resolved render device path (may be empty on non-Linux platforms). + */ + std::string resolve_render_device(); /** * @brief Gets the supported gamepads for this platform backend. diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index df6d7f365ab..6839c0bb05a 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -1218,4 +1218,12 @@ namespace platf { #endif } + std::string resolve_render_device() { + if (!config::video.adapter_name.empty()) { + return config::video.adapter_name; + } + auto detected = find_render_node_with_display(); + return detected.empty() ? "/dev/dri/renderD128" : detected; + } + } // namespace platf diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 18d7ff48dda..a82afce10e1 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -522,9 +522,7 @@ namespace va { va::display_t display {vaGetDisplayDRM(fd)}; if (!display) { - auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); - - BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << render_device; + BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << platf::resolve_render_device(); return -1; } @@ -642,9 +640,9 @@ namespace va { } std::unique_ptr make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) { - auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); + auto render_device = platf::resolve_render_device(); - file_t file = open(render_device, O_RDWR); + file_t file = open(render_device.c_str(), O_RDWR); if (file.el < 0) { char string[1024]; BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string)); diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index bb114241074..525daa194f7 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -79,9 +79,8 @@ namespace vk { static int create_vulkan_hwdevice(AVBufferRef **hw_device_buf) { // Resolve render device path to Vulkan device index - auto detected = platf::find_render_node_with_display(); - auto fallback = detected.empty() ? std::string("/dev/dri/renderD128") : detected; - if (auto render_path = config::video.adapter_name.empty() ? fallback : config::video.adapter_name; render_path[0] == '/') { + auto render_path = platf::resolve_render_device(); + if (render_path[0] == '/') { if (auto idx = find_vulkan_index_for_render_node(render_path.c_str()); !idx.empty() && av_hwdevice_ctx_create(hw_device_buf, AV_HWDEVICE_TYPE_VULKAN, idx.c_str(), nullptr, 0) >= 0) { return 0; } diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 89edae10ca0..12edfd7fc14 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -561,7 +561,7 @@ operator bool() override { return std::make_unique(); } - std::string find_render_node_with_display() { + std::string resolve_render_device() { return {}; } } // namespace platf diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index fb94860127b..32bfef15943 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1792,7 +1792,7 @@ namespace platf { return true; } - std::string find_render_node_with_display() { + std::string resolve_render_device() { return {}; } } // namespace platf diff --git a/src/video.cpp b/src/video.cpp index 45c6201bc8a..17106133f7c 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2994,10 +2994,9 @@ namespace video { return hw_device_buf; } - auto detected = platf::find_render_node_with_display(); - auto render_device = config::video.adapter_name.empty() ? detected.c_str() : config::video.adapter_name.c_str(); + auto render_device = platf::resolve_render_device(); - auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VAAPI, render_device[0] ? render_device : nullptr, nullptr, 0); + auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VAAPI, render_device.empty() ? nullptr : render_device.c_str(), nullptr, 0); if (status < 0) { char string[AV_ERROR_MAX_STRING_SIZE]; BOOST_LOG(error) << "Failed to create a VAAPI device: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); @@ -3021,11 +3020,9 @@ namespace video { } // Try render device path first, auto-detecting the GPU with a connected display - auto detected = platf::find_render_node_with_display(); - auto fallback = detected.empty() ? std::string("/dev/dri/renderD128") : detected; - auto render_device = config::video.adapter_name.empty() ? fallback.c_str() : config::video.adapter_name.c_str(); + auto render_device = platf::resolve_render_device(); - auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VULKAN, render_device, nullptr, 0); + auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_VULKAN, render_device.c_str(), nullptr, 0); if (status >= 0) { BOOST_LOG(info) << "Using Vulkan device: "sv << render_device; return hw_device_buf; From f96eb8309654f463d22317b8ba0cce25faf12947 Mon Sep 17 00:00:00 2001 From: neatnoise Date: Thu, 16 Apr 2026 19:49:05 +0200 Subject: [PATCH 3/4] fix: qualify POSIX open() and use C++17 if-init for render_path --- src/platform/linux/vaapi.cpp | 2 +- src/platform/linux/vulkan_encode.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index a82afce10e1..6ca0e14dde9 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -642,7 +642,7 @@ namespace va { std::unique_ptr make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) { auto render_device = platf::resolve_render_device(); - file_t file = open(render_device.c_str(), O_RDWR); + file_t file = ::open(render_device.c_str(), O_RDWR); if (file.el < 0) { char string[1024]; BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string)); diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index 525daa194f7..3e49fad7af1 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -79,8 +79,7 @@ namespace vk { static int create_vulkan_hwdevice(AVBufferRef **hw_device_buf) { // Resolve render device path to Vulkan device index - auto render_path = platf::resolve_render_device(); - if (render_path[0] == '/') { + if (auto render_path = platf::resolve_render_device(); render_path[0] == '/') { if (auto idx = find_vulkan_index_for_render_node(render_path.c_str()); !idx.empty() && av_hwdevice_ctx_create(hw_device_buf, AV_HWDEVICE_TYPE_VULKAN, idx.c_str(), nullptr, 0) >= 0) { return 0; } From d56679dcfe049afaf774ae9db087dc90aba3de00 Mon Sep 17 00:00:00 2001 From: David Lane <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:18:02 -0400 Subject: [PATCH 4/4] style: ignore sonar warning --- src/platform/linux/vaapi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 6ca0e14dde9..525ef76b6fb 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -642,7 +642,7 @@ namespace va { std::unique_ptr make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) { auto render_device = platf::resolve_render_device(); - file_t file = ::open(render_device.c_str(), O_RDWR); + file_t file = ::open(render_device.c_str(), O_RDWR); // NOSONAR(cpp:S1874) - `_sopen_s` not available if (file.el < 0) { char string[1024]; BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string));