Skip to content

relil/gamescope-eGPU

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gamescope --prefer-drm — independent scanout device selection

A small, upstream-friendly patch for ValveSoftware/gamescope that lets you choose the DRM/KMS scanout (display) device independently of the Vulkan render device. This makes gamescope usable on hybrid / eGPU / muxless / split-SoC systems where the GPU you want to render on is not the GPU the monitor is attached to.

  • Branch: feature/prefer-drm
  • Patch file: 0001-prefer-drm.patch
  • Files touched: src/main.cpp, src/main.hpp, src/backends.h, src/Backends/DRMBackend.cpp (+126 / −10)
  • Related upstream issue: #2070 (and #158, #1469, #1643)

English

The problem

gamescope's DRM backend derives the KMS (scanout) device from the selected Vulkan physical device. In init_drm() it calls vulkan_primary_dev_id(), looks the device up with drmGetDeviceFromDevId(), and opens that GPU's DRM_NODE_PRIMARY.

As a result, --prefer-vk-device effectively picks both the render GPU and the scanout GPU. On a machine where the fast GPU is headless (no connectors) — a render-only accelerator, an eGPU in some muxless modes, or an ARM SoC whose GPU and display controller are separate DRM devices — selecting that GPU for rendering also forces scanout onto it. It has no connected outputs, so you get the classic:

drm: opening DRM node '/dev/dri/card1'
drm: '/dev/dri/card1' is not a KMS device
vulkan_make_output failed

…i.e. a failed start / black screen. There was previously no way to say “render on GPU A, scan out on GPU B”.

The solution

A new option:

--prefer-drm <device>

It selects the DRM/KMS device used for scanout/output independently of the Vulkan device. The three selectors now compose cleanly:

Option Chooses
--prefer-vk-device 1002:7360 the Vulkan render/compositor GPU
--prefer-drm /dev/dri/by-path/... the DRM/KMS scanout device
-O, --prefer-output HDMI-A-1 the connector within the chosen DRM device

Usage

gamescope \
  --prefer-drm /dev/dri/by-path/pci-0000:00:02.0-card \
  --prefer-vk-device 1002:7360 \
  -O HDMI-A-2 \
  -- steam -gamepadui

Accepted forms for --prefer-drm:

  • absolute primary node — /dev/dri/card0
  • short form — card0 (expanded to /dev/dri/card0)
  • by-path symlink — /dev/dri/by-path/pci-0000:00:02.0-card (resolved via realpath)

Behaviour & validation

  • Default behaviour is unchanged. If neither --prefer-drm nor the env var is set, the original Vulkan-device-derived path is used verbatim.
  • Env fallback. When the flag is absent, the value is also read from $GAMESCOPE_PREFER_DRM, so a session manager can configure a split render/scanout setup without per-game launch options (the flag takes precedence).
  • The value is resolved and validated up-front in main() (DRMBackendCheckPreferredDevice()), before the backend and the heavy Vulkan/session init are brought up, so bad input fails fast with a clear message rather than tearing down a half-initialised backend.
  • Validation does not open() the node. Under seatd/logind the compositor usually cannot open a primary node directly (that is the session manager's job), so a plain open() would spuriously fail with EACCES on perfectly valid setups. Instead the device number is inspected (stat()): DRM primary minors are 0–63, render nodes are 128+, so render nodes are rejected. The authoritative drmIsKMS() check still runs later, once the session has opened the fd.

Logging

drm: --prefer-drm: using DRM scanout device '/dev/dri/card0'
drm: Connectors:
drm:   HDMI-A-2 (connected)
drm:   DP-1 (disconnected)
drm: preferred output (--prefer-output): HDMI-A-2
drm: selecting connector HDMI-A-2
drm: selecting mode 1920x1200@60Hz

Building / applying

For a step-by-step, distribution-specific build guide, see BUILD.md (CachyOS/Arch, Bazzite, generic). Build the patch against the same gamescope version already installed on the system — the WSI layer must match.

git clone --recursive https://github.com/ValveSoftware/gamescope
cd gamescope
git checkout <your installed version, e.g. 3.16.23>   # match your system!
git apply /path/to/0001-prefer-drm.patch     # or: git am
meson setup build
ninja -C build

Distro packaging

  • CachyOS / Arch (mutable, pacman -U): cachyos/ — a PKGBUILD

    • prebuilt packages (see Releases). CachyOS ships gamescope 3.16.24-1 (extra) and 3.16.20/gamescope-git (cachyos repo) plus the gamescope-session-cachyos Big Picture session.

    Quick start — run the UI on the external card (full step-by-step, incl. how to find your own device IDs, in cachyos/README.md):

    # 1. install the patched gamescope matching your version (from Releases)
    sudo pacman -U ./gamescope-3.16.23-1-x86_64.pkg.tar.zst
    
    # 2. two GLOBAL env vars in ~/.config/environment.d/ — no session edits:
    mkdir -p ~/.config/environment.d
    # render UI + games on the external/fast GPU (vendor:device, e.g. AMD BC-160):
    echo 'MESA_VK_DEVICE_SELECT=1002:7360' \
      > ~/.config/environment.d/05-gamescope-render-gpu.conf
    # output to the monitor through the display GPU (by-path of the connected card):
    echo 'GAMESCOPE_PREFER_DRM=/dev/dri/by-path/pci-0000:00:02.0-card' \
      > ~/.config/environment.d/10-gamescope-prefer-drm.conf
    
    # 3. reboot — Game Mode picks them up

    MESA_VK_DEVICE_SELECT = which GPU renders; GAMESCOPE_PREFER_DRM = which GPU outputs to the monitor. Edit both to your hardware (lspci -nn, ls -l /dev/dri/by-path/).

  • Bazzite (immutable bootc): bazzite/ — the patch goes upstream; export-gpu is extended to write $GAMESCOPE_PREFER_DRM. A runtime rpm-ostree override replace does not apply on container-native images, so use a custom image or the userspace GAMESCOPE_BIN hook.

Tested

On an Intel HD 530 iGPU (card0, HDMI-A-2) + AMD BC-160 (1002:7360, headless) machine running Bazzite / Steam Game Mode:

  • --help lists the new option.
  • Intel scanout + Intel render and Intel scanout + BC-160 render both bring the output up on HDMI-A-2 at 1920x1200@60, compositing on RADV NAVI12 (BC-160), with no atomic / addfb2 / modifier / dmabuf errors.
  • Without the flag, gamescope still selects the BC-160 primary node and fails with is not a KMS device — the exact black screen this fixes.
  • Invalid path and render-node input fail fast with a clear error and a clean exit.
  • Real Steam Game Mode: with the patched binary wired in via GAMESCOPE_BIN, Steam Big Picture comes up on the Intel-driven monitor while the compositor renders on the BC-160.

Known limitations / notes

  • This patch covers the compositor render device + scanout device split. It does not force games onto a particular GPU — a game launched inside gamescope still selects its own Vulkan device. Steering games onto the render GPU seamlessly (without per-game launch options) is a separate follow-up using child-process environment (MESA_VK_DEVICE_SELECT / DRI_PRIME).
  • Cross-GPU scanout relies on the destination KMS device accepting the render GPU's buffers/modifiers. It worked without modifier fallbacks on the tested hardware; a linear/modifier-less fallback is a possible follow-up for stricter KMS drivers.
  • The minor < 64 primary-node check mirrors libdrm's internal node-numbering convention.

Possible follow-ups

  1. --prefer-drm pci-0000:03:00.0 PCI-address shorthand.
  2. Optional linear / modifier-less framebuffer fallback for cross-GPU scanout.
  3. Seamless per-child game GPU selection on the render GPU.

Session integration is done: $GAMESCOPE_PREFER_DRM is read as a fallback, and bazzite/ proposes the matching export-gpu change for Bazzite Game Mode.


Русский

Проблема

DRM-бэкенд gamescope берёт KMS-устройство (для вывода) из выбранного Vulkan-устройства. В init_drm() вызывается vulkan_primary_dev_id(), устройство ищется через drmGetDeviceFromDevId(), и открывается его DRM_NODE_PRIMARY.

Из-за этого --prefer-vk-device фактически выбирает и GPU для рендера, и GPU для вывода. На системе, где быстрый GPU безмониторный (нет коннекторов) — render-only ускоритель, eGPU в некоторых muxless-режимах, или ARM-SoC, где GPU и контроллер дисплея — разные DRM-устройства — выбор такого GPU для рендера принудительно отправляет на него же и scanout. Подключённых выходов у него нет, и получается классическое:

drm: opening DRM node '/dev/dri/card1'
drm: '/dev/dri/card1' is not a KMS device
vulkan_make_output failed

…то есть провал старта / чёрный экран. Раньше не было способа сказать «рендерить на GPU A, выводить через GPU B».

Решение

Новый параметр:

--prefer-drm <device>

Он задаёт DRM/KMS-устройство для scanout/вывода независимо от Vulkan-устройства. Теперь три селектора аккуратно дополняют друг друга:

Параметр Что выбирает
--prefer-vk-device 1002:7360 Vulkan-GPU для рендера/компоновки
--prefer-drm /dev/dri/by-path/... DRM/KMS-устройство для вывода
-O, --prefer-output HDMI-A-1 коннектор внутри выбранного DRM-устройства

Использование

gamescope \
  --prefer-drm /dev/dri/by-path/pci-0000:00:02.0-card \
  --prefer-vk-device 1002:7360 \
  -O HDMI-A-2 \
  -- steam -gamepadui

Поддерживаемые формы --prefer-drm:

  • абсолютный primary node — /dev/dri/card0
  • короткая форма — card0 (раскрывается в /dev/dri/card0)
  • by-path-симлинк — /dev/dri/by-path/pci-0000:00:02.0-card (резолвится через realpath)

Поведение и валидация

  • Поведение по умолчанию не меняется. Если не задан ни флаг, ни env-переменная, используется прежний путь (устройство из Vulkan-девайса), без изменений.
  • Env-fallback. Если флаг не задан, значение берётся из $GAMESCOPE_PREFER_DRM — чтобы менеджер сессии мог настроить split render/scanout без per-game опций (флаг имеет приоритет).
  • Значение резолвится и проверяется заранее, в main() (DRMBackendCheckPreferredDevice()), до создания бэкенда и тяжёлой инициализации Vulkan/сессии — поэтому невалидный ввод завершается сразу с понятным сообщением, а не ломает наполовину инициализированный бэкенд.
  • Валидация не делает open() узла. Под seatd/logind компоновщик обычно не может открыть primary node напрямую (это задача менеджера сессий), так что обычный open() ложно падал бы с EACCES на полностью корректных конфигурациях. Вместо этого проверяется номер устройства (stat()): у DRM primary node младший номер 0–63, у render-узлов 128+, поэтому render-узлы отвергаются. Авторитетная проверка drmIsKMS() по-прежнему выполняется позже, когда сессия откроет дескриптор.

Логирование

drm: --prefer-drm: using DRM scanout device '/dev/dri/card0'
drm: Connectors:
drm:   HDMI-A-2 (connected)
drm:   DP-1 (disconnected)
drm: preferred output (--prefer-output): HDMI-A-2
drm: selecting connector HDMI-A-2
drm: selecting mode 1920x1200@60Hz

Сборка / применение

Пошаговое руководство по сборке для конкретных дистрибутивов — BUILD.md (CachyOS/Arch, Bazzite, generic). Патч собирается на той же версии gamescope, что установлена в системе — WSI-слой должен совпадать.

git clone --recursive https://github.com/ValveSoftware/gamescope
cd gamescope
git checkout <ваша установленная версия, напр. 3.16.23>   # совпадение обязательно!
git apply /path/to/0001-prefer-drm.patch     # или: git am
meson setup build
ninja -C build

Упаковка под дистрибутивы

  • CachyOS / Arch (mutable, pacman -U): cachyos/ — PKGBUILD + готовые пакеты в Releases. В CachyOS gamescope идёт из extra (3.16.24-1) и cachyos (3.16.20/gamescope-git), сессия — gamescope-session-cachyos.

    Быстрый старт — UI на внешней карте (полный пошаговый гайд с поиском своих device ID — в cachyos/README.md):

    # 1. поставить патченный gamescope нужной версии (из Releases)
    sudo pacman -U ./gamescope-3.16.23-1-x86_64.pkg.tar.zst
    
    # 2. две ГЛОБАЛЬНЫЕ переменные в ~/.config/environment.d/ — без правки сессии:
    mkdir -p ~/.config/environment.d
    # рендер UI + игр на внешней/мощной GPU (vendor:device, напр. AMD BC-160):
    echo 'MESA_VK_DEVICE_SELECT=1002:7360' \
      > ~/.config/environment.d/05-gamescope-render-gpu.conf
    # вывод на монитор через дисплейную GPU (by-path подключённой карты):
    echo 'GAMESCOPE_PREFER_DRM=/dev/dri/by-path/pci-0000:00:02.0-card' \
      > ~/.config/environment.d/10-gamescope-prefer-drm.conf
    
    # 3. перезагрузка — Game Mode подхватит переменные

    MESA_VK_DEVICE_SELECT = какая GPU рендерит; GAMESCOPE_PREFER_DRM = какая GPU выводит на монитор. Подставьте свои значения (lspci -nn, ls -l /dev/dri/by-path/).

  • Bazzite (immutable bootc): bazzite/ — патч идёт в upstream; export-gpu расширен и пишет $GAMESCOPE_PREFER_DRM. Runtime rpm-ostree override replace на container-native образах не применяется — используйте кастомный образ или userspace-хук GAMESCOPE_BIN.

Протестировано

На машине с Intel HD 530 iGPU (card0, HDMI-A-2) + AMD BC-160 (1002:7360, безмониторный) под Bazzite / Steam Game Mode:

  • --help показывает новый параметр.
  • Intel scanout + Intel render и Intel scanout + BC-160 render оба поднимают вывод на HDMI-A-2 в 1920x1200@60, с композитингом на RADV NAVI12 (BC-160), без ошибок atomic / addfb2 / modifier / dmabuf.
  • Без флага gamescope по-прежнему выбирает primary node BC-160 и падает с is not a KMS device — ровно тот чёрный экран, который это и чинит.
  • Несуществующий путь и render-node завершаются сразу с понятной ошибкой и чистым выходом.
  • Реальный Steam Game Mode: с патченным бинарём, подключённым через GAMESCOPE_BIN, Steam Big Picture выводится на монитор от Intel, а компоновщик рендерит на BC-160.

Известные ограничения / примечания

  • Патч покрывает разделение GPU рендера компоновщика + устройство вывода. Он не форсирует игры на конкретный GPU — игра, запущенная внутри gamescope, всё ещё выбирает свой Vulkan-девайс сама. Бесшовное направление игр на render-GPU (без per-game опций) — отдельный follow-up через окружение дочерних процессов (MESA_VK_DEVICE_SELECT / DRI_PRIME).
  • Межгпушный scanout зависит от того, принимает ли целевое KMS-устройство буферы/модификаторы render-GPU. На тестовом железе это сработало без modifier-фолбэков; linear/modifier-less фолбэк — возможный follow-up для более строгих KMS-драйверов.
  • Проверка primary-узла по minor < 64 повторяет внутреннее соглашение нумерации узлов в libdrm.

Возможные follow-up'ы

  1. Сокращение --prefer-drm pci-0000:03:00.0 по PCI-адресу.
  2. Опциональный linear / modifier-less фреймбуфер-фолбэк для межгпушного scanout.
  3. Бесшовный выбор GPU для дочерних игровых процессов на render-GPU.

Интеграция с сессией сделана: читается fallback $GAMESCOPE_PREFER_DRM, а в bazzite/ — предложение по правке export-gpu для Bazzite Game Mode.

About

gamescope --prefer-drm: render on one GPU, scan out through another. Upstream-style patch for hybrid / eGPU / headless-render setups, with CachyOS & Bazzite packaging.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages