fix(linux): auto-detect GPU with connected display for VAAPI and Vulkan (#4961)

This commit is contained in:
neatnoise
2026-04-17 20:38:06 +02:00
committed by GitHub
parent b31948594c
commit 3210078310
7 changed files with 89 additions and 11 deletions

View File

@@ -861,6 +861,15 @@ namespace platf {
*/
std::string get_host_name();
/**
* @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.
* @details This may be called prior to `platf::input()`!

View File

@@ -38,6 +38,12 @@
#include <fcntl.h>
#include <unistd.h>
#ifdef SUNSHINE_BUILD_DRM
#include <dirent.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#endif
// local includes
#include "graphics.h"
#include "misc.h"
@@ -1163,4 +1169,61 @@ namespace platf {
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
return std::make_unique<linux_high_precision_timer>();
}
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
}
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

View File

@@ -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<platf::avcodec_encode_device_t> 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); // 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));

View File

@@ -79,7 +79,7 @@ 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] == '/') {
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;
}

View File

@@ -560,6 +560,10 @@ namespace platf {
std::unique_ptr<high_precision_timer> create_high_precision_timer() {
return std::make_unique<macos_high_precision_timer>();
}
std::string resolve_render_device() {
return {};
}
} // namespace platf
namespace dyn {

View File

@@ -1791,4 +1791,8 @@ namespace platf {
return true;
}
std::string resolve_render_device() {
return {};
}
} // namespace platf

View File

@@ -2994,9 +2994,9 @@ namespace video {
return hw_device_buf;
}
auto render_device = config::video.adapter_name.empty() ? nullptr : 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, 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);
@@ -3019,10 +3019,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 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;